diff --git a/lib/windows_x64 b/lib/windows_x64 index a5521c85e03..19b2b87f5ef 160000 --- a/lib/windows_x64 +++ b/lib/windows_x64 @@ -1 +1 @@ -Subproject commit a5521c85e03bfd1556ff1e63bf7163235c401497 +Subproject commit 19b2b87f5ef0d8caa39e0882fbf832052974b785 diff --git a/scripts/startup/bl_ui/properties_paint_common.py b/scripts/startup/bl_ui/properties_paint_common.py index c3a3fb97afc..1ce4d4ea998 100644 --- a/scripts/startup/bl_ui/properties_paint_common.py +++ b/scripts/startup/bl_ui/properties_paint_common.py @@ -118,6 +118,38 @@ class UnifiedPaintPanel: return row + @staticmethod + def get_dyntopo_prop(context, brush, prop_name): + sculpt = context.tool_settings.sculpt + + if not getattr(brush.dyntopo, "is_" + prop_name + "_overridden"): + return getattr(sculpt.dyntopo, prop_name) + else: + return getattr(brush.dyntopo, prop_name) + + @staticmethod + def prop_unified_dyntopo( + layout, context, brush, prop_name, text=None, expand=False + ): + sculpt = context.tool_settings.sculpt + + override_name = "is_" + prop_name + "_overridden" + inherit = not getattr(brush.dyntopo, override_name) + + if inherit: + final_dyntopo = sculpt.dyntopo + else: + final_dyntopo = brush.dyntopo + + layout = layout.row(align=True) + #layout.enabled = inherit + layout.prop(final_dyntopo, prop_name, text=text, expand=expand) + + if not inherit: + layout.prop( + brush.dyntopo, override_name, text="", icon='ERROR', emboss=False + ) + @staticmethod def prop_unified_color(parent, context, brush, prop_name, *, text=None): ups = context.tool_settings.unified_paint_settings @@ -563,7 +595,11 @@ def brush_settings(layout, context, brush, popover=False): row.prop(brush, "invert_hardness_pressure", text="") row.prop(brush, "use_hardness_pressure", text="") - # auto_smooth_factor and use_inverse_smooth_pressure + layout.prop(brush.dyntopo, "disabled") + + layout.separator() + layout.label(text="Smooth Settings") + if capabilities.has_auto_smooth: UnifiedPaintPanel.prop_unified( layout, @@ -574,12 +610,41 @@ def brush_settings(layout, context, brush, popover=False): slider=True, ) + if capabilities.has_auto_smooth or brush.sculpt_tool == 'SMOOTH': + UnifiedPaintPanel.prop_unified( + layout, + context, + brush, + "auto_smooth_projection", + slider=True, + text="Preserve Volume", + ) + + is_smooth = capabilities.has_auto_smooth + is_smooth = is_smooth or brush.sculpt_tool in {'SMOOTH', 'SIMPLIFY'} + hard_edge = context.tool_settings.unified_paint_settings.hard_edge_mode + + if is_smooth: + UnifiedPaintPanel.prop_unified( + layout, + context, + brush, + "hard_corner_pin", + slider=True, + unified_name="use_unified_hard_corner_pin", + text="Corner Pin", + ) + + if capabilities.has_auto_smooth or brush.sculpt_tool == 'SMOOTH': + layout.prop(brush, "use_weighted_smooth") + # topology_rake_factor if ( capabilities.has_topology_rake and context.sculpt_object.use_dynamic_topology_sculpting ): layout.prop(brush, "topology_rake_factor", slider=True) + layout.prop(brush, "use_curvature_rake") # normal_weight if capabilities.has_normal_weight: @@ -959,6 +1024,8 @@ def brush_settings_advanced(layout, context, brush, popover=False): use_accumulate = capabilities.has_accumulate use_frontface = True + layout.prop(brush.dyntopo, "disabled") + col = layout.column(heading="Auto-Masking", align=True) col.prop(brush, "use_automasking_topology", text="Topology") diff --git a/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/scripts/startup/bl_ui/space_toolsystem_toolbar.py index 42e774ed35e..6a3898f68b3 100644 --- a/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -1543,6 +1543,10 @@ class _defs_sculpt: row = layout.row(align=True) row.prop(props, "deform_axis") layout.prop(props, "orientation", expand=False) + + if props.type in {'SMOOTH', 'SURFACE_SMOOTH', 'ENHANCE_DETAILS', 'SHARPEN'}: + layout.prop(props, "hard_corner_pin", expand=False) + if props.type == 'SURFACE_SMOOTH': layout.prop(props, "surface_smooth_shape_preservation", expand=False) layout.prop(props, "surface_smooth_current_vertex", expand=False) diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index 64a44d34bea..e7a1f891c23 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -7323,6 +7323,7 @@ class VIEW3D_PT_overlay_edit_curve(Panel): sub.prop(overlay, "normals_length", text="Normals") + class VIEW3D_PT_overlay_sculpt(Panel): bl_space_type = 'VIEW_3D' bl_context = ".sculpt_mode" @@ -7353,7 +7354,6 @@ class VIEW3D_PT_overlay_sculpt(Panel): sub.active = overlay.show_sculpt_face_sets row.prop(overlay, "sculpt_mode_face_sets_opacity", text="Face Sets") - class VIEW3D_PT_overlay_sculpt_curves(Panel): bl_space_type = 'VIEW_3D' bl_context = ".curves_sculpt" diff --git a/scripts/startup/bl_ui/space_view3d_toolbar.py b/scripts/startup/bl_ui/space_view3d_toolbar.py index babbb5f1cc6..59839c7a314 100644 --- a/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -972,7 +972,7 @@ class VIEW3D_PT_tools_brush_falloff_normal(View3DPaintPanel, Panel): # TODO, move to space_view3d.py class VIEW3D_PT_sculpt_dyntopo(Panel, View3DPaintPanel): bl_context = ".sculpt_mode" # dot on purpose (access from topbar) - bl_label = "Dyntopo" + bl_label = "Dynamic Topology" bl_options = {'DEFAULT_CLOSED'} bl_ui_units_x = 12 @@ -1004,23 +1004,119 @@ class VIEW3D_PT_sculpt_dyntopo(Panel, View3DPaintPanel): col = layout.column() col.active = context.sculpt_object.use_dynamic_topology_sculpting + col.prop(sculpt, "use_dyntopo") + sub = col.column() - sub.active = (brush and brush.sculpt_tool != 'MASK') - if sculpt.detail_type_method in {'CONSTANT', 'MANUAL'}: + sub.active = bool(brush) + + detail_mode = UnifiedPaintPanel.get_dyntopo_prop(context, brush, "mode") + + if detail_mode in {'CONSTANT', 'MANUAL'}: row = sub.row(align=True) - row.prop(sculpt, "constant_detail_resolution") + + UnifiedPaintPanel.prop_unified_dyntopo( + row, + context, + brush, + "constant_detail" + ) + props = row.operator("sculpt.sample_detail_size", text="", icon='EYEDROPPER') props.mode = 'DYNTOPO' - elif (sculpt.detail_type_method == 'BRUSH'): - sub.prop(sculpt, "detail_percent") + elif detail_mode == 'BRUSH': + UnifiedPaintPanel.prop_unified_dyntopo( + sub, + context, + brush, + "detail_percent" + ) else: - sub.prop(sculpt, "detail_size") - sub.prop(sculpt, "detail_refine_method", text="Refine Method") - sub.prop(sculpt, "detail_type_method", text="Detailing") + UnifiedPaintPanel.prop_unified_dyntopo( + sub, + context, + brush, + "detail_size" + ) - if sculpt.detail_type_method in {'CONSTANT', 'MANUAL'}: + UnifiedPaintPanel.prop_unified_dyntopo( + sub, + context, + brush, + "subdivide" + ) + + UnifiedPaintPanel.prop_unified_dyntopo( + sub, + context, + brush, + "collapse" + ) + + UnifiedPaintPanel.prop_unified_dyntopo( + sub, + context, + brush, + "cleanup" + ) + + UnifiedPaintPanel.prop_unified_dyntopo( + sub, + context, + brush, + "mode", + expand=False + ) + + #NotForPR + if 0: + scene = context.scene + def do_prop(name, text=None): + if text is None: text = name + + if name in scene: + sub.prop(scene, "[\"%s\"]" % name, text=text) + + do_prop("dparam1", text="p0") + do_prop("dparam2", text="p1") + do_prop("dparam3", text="p2") + do_prop("dparam4", text="p3") + do_prop("dparam5", text="p4") + + if UnifiedPaintPanel.get_dyntopo_prop(context, brush, "mode") in {'CONSTANT', 'MANUAL'}: col.operator("sculpt.detail_flood_fill") + col.prop(WindowManager.operator_properties_last("sculpt.detail_flood_fill"), "interactive") + col.prop(WindowManager.operator_properties_last("sculpt.detail_flood_fill"), "developer") + if WindowManager.operator_properties_last("sculpt.detail_flood_fill").developer: + col.prop(WindowManager.operator_properties_last("sculpt.detail_flood_fill"), "emulate_brush") + UnifiedPaintPanel.prop_unified_dyntopo( + sub, + context, + brush, + "spacing", + expand=True + ) + UnifiedPaintPanel.prop_unified_dyntopo( + sub, + context, + brush, + "radius_scale", + expand=True + ) + UnifiedPaintPanel.prop_unified_dyntopo( + sub, + context, + brush, + "repeat", + expand=True + ) + UnifiedPaintPanel.prop_unified_dyntopo( + sub, + context, + brush, + "quality", + expand=True + ) class VIEW3D_PT_sculpt_voxel_remesh(Panel, View3DPaintPanel): bl_context = ".sculpt_mode" # dot on purpose (access from topbar) @@ -1071,12 +1167,58 @@ class VIEW3D_PT_sculpt_options(Panel, View3DPaintPanel): tool_settings = context.tool_settings sculpt = tool_settings.sculpt + brush = sculpt.brush + ups = tool_settings.unified_paint_settings col = layout.column(heading="Display", align=True) col.prop(sculpt, "show_low_resolution") col.prop(sculpt, "use_sculpt_delay_updates") col.prop(sculpt, "use_deform_only") + import bpy + if bpy.app.debug_value == 889: + col.prop(tool_settings, "show_origco") + + col.prop(ups, "distort_correction_mode") + + col.label(text="Smooth Boundaries") + col = layout.column(align=True) + + col.prop(ups, "smooth_boundary_seam", text="Relax Marked Seams") + col.prop(ups, "smooth_boundary_uv", text="Relax UV Seams") + col.prop(ups, "smooth_boundary_face_set", text="Relax Face Sets") + + col.separator(); + + row = col.row() + row.enabled = ups.smooth_boundary_face_set + row.prop(ups, "hard_edge_mode", text="Crease Face Sets") + + UnifiedPaintPanel.prop_unified( + col, + context, + brush, + "hard_corner_pin", + slider=True, + unified_name = "use_unified_hard_corner_pin", + text="Corner Pin" + ) + + col.separator(); + + col.prop(ups, "smooth_boundary_mesh", text="Crease Boundaries") + col.prop(ups, "smooth_boundary_sharp_mark", text="Crease Marked Sharp") + col.prop(ups, "smooth_boundary_sharp_angle", text="Crease By Angle") + + row = col.row() + row.enabled = ups.smooth_boundary_sharp_angle + UnifiedPaintPanel.prop_unified( + row, + context, + tool_settings.unified_paint_settings, + "sharp_angle_limit", + unified_name = "use_unified_sharp_angle_limit" + ) class VIEW3D_PT_sculpt_options_gravity(Panel, View3DPaintPanel): bl_context = ".sculpt_mode" # dot on purpose (access from topbar) diff --git a/source/blender/blenkernel/BKE_brush.hh b/source/blender/blenkernel/BKE_brush.hh index ec7d758057d..a6a352fbb2f 100644 --- a/source/blender/blenkernel/BKE_brush.hh +++ b/source/blender/blenkernel/BKE_brush.hh @@ -22,6 +22,9 @@ struct MTex; struct Scene; struct ToolSettings; struct UnifiedPaintSettings; +struct DynTopoSettings; +struct Sculpt; +struct CurveMapping; // enum eCurveMappingPreset; @@ -56,6 +59,12 @@ Brush *BKE_brush_first_search(Main *bmain, eObjectMode ob_mode); void BKE_brush_sculpt_reset(Brush *brush); +/* Which dyntopo settings are inherited by this brush from scene + * defaults. In most cases this is everything except for the + * local dyntopo disable flag. + */ +int BKE_brush_dyntopo_inherit_flags(Brush *brush); + /** * Create a set of grease pencil Drawing presets. */ @@ -158,7 +167,6 @@ void BKE_brush_input_samples_set(const Scene *scene, Brush *brush, int value); bool BKE_brush_use_locked_size(const Scene *scene, const Brush *brush); bool BKE_brush_use_alpha_pressure(const Brush *brush); bool BKE_brush_use_size_pressure(const Brush *brush); - bool BKE_brush_sculpt_has_secondary_color(const Brush *brush); /** @@ -175,6 +183,8 @@ void BKE_brush_scale_size(int *r_brush_size, float new_unprojected_radius, float old_unprojected_radius); +void BKE_brush_default_input_curves_set(Brush *brush); + /* Returns true if a brush requires a cube * (often presented to the user as a square) tip inside a specific paint mode. */ @@ -193,3 +203,13 @@ bool BKE_brush_has_cube_tip(const Brush *brush, PaintMode paint_mode); /* debugging only */ void BKE_brush_debug_print_state(Brush *br); + +void BKE_brush_get_dyntopo(Brush *brush, Sculpt *sd, DynTopoSettings *out); + +bool BKE_brush_hard_edge_mode_get(const Scene *scene, const Brush *brush); +void BKE_brush_hard_edge_mode_set(Scene *scene, Brush *brush, bool val); +float BKE_brush_hard_corner_pin_get(const Scene *scene, const Brush *brush); + +float BKE_brush_fset_slide_get(const Scene *scene, const Brush *brush); +float BKE_brush_curve_strength_ex( + int curve_preset, const CurveMapping *curve, float p, const float len, const bool invert); diff --git a/source/blender/blenkernel/BKE_customdata.hh b/source/blender/blenkernel/BKE_customdata.hh index 491dcd04ba7..cc0292f13de 100644 --- a/source/blender/blenkernel/BKE_customdata.hh +++ b/source/blender/blenkernel/BKE_customdata.hh @@ -122,6 +122,8 @@ bool CustomData_has_interp(const CustomData *data); */ bool CustomData_bmesh_has_free(const CustomData *data); +bool CustomData_layout_is_same(const struct CustomData *_a, const struct CustomData *_b); + /** * Checks if any of the custom-data layers is referenced. */ @@ -175,6 +177,12 @@ void CustomData_copy_layout(const CustomData *source, /* BMESH_TODO, not really a public function but `readfile.cc` needs it. */ void CustomData_update_typemap(CustomData *data); +/* Copies all customdata layers without allocating data + * and without respect to type masks or CD_FLAG_NO_COPY + * or CD_FLAG_TEMPORARY flags. + */ +void CustomData_copy_all_layout(const struct CustomData *source, struct CustomData *dest); + /** * Copies all layers from source to destination that don't exist there yet. */ @@ -344,6 +352,16 @@ void CustomData_copy_data_layer(const CustomData *source, int src_index, int dst_index, int count); + +void CustomData_bmesh_poison(const struct CustomData *cdata, void *block); +void CustomData_bmesh_unpoison(const struct CustomData *cdata, void *block); + +/* Swap attributes. Does not respect CD_FLAG_ELEM_NOCOPY. */ +void CustomData_bmesh_swap_data(struct CustomData *source, + struct CustomData *dest, + void *src_block, + void **dest_block); + void CustomData_copy_data_named( const CustomData *source, CustomData *dest, int source_index, int dest_index, int count); void CustomData_copy_elements(eCustomDataType type, @@ -357,6 +375,12 @@ void CustomData_copy_elements(eCustomDataType type, */ void CustomData_bmesh_copy_block(CustomData &data, void *src_block, void **dst_block); +/** XXX deprecated */ +void CustomData_bmesh_copy_data(const CustomData *source, + CustomData *dest, + void *src_block, + void **dest_block); + /** Holds the minimal data necessary to copy data blocks from one custom data format to another. */ struct BMCustomDataCopyMap { struct TrivialCopy { @@ -454,6 +478,13 @@ void CustomData_bmesh_interp(CustomData *data, const float *sub_weights, int count, void *dst_block); +void CustomData_bmesh_interp_ex(struct CustomData *data, + const void **src_blocks, + const float *weights, + const float *sub_weights, + int count, + void *dst_block, + eCustomDataMask typemask); /** * Swap data inside each item, for all layers. @@ -803,6 +834,7 @@ void CustomData_blend_write(BlendWriter *writer, void CustomData_blend_read(BlendDataReader *reader, CustomData *data, int count); size_t CustomData_get_elem_size(const CustomDataLayer *layer); +void CustomData_regen_active_refs(CustomData *data); #ifndef NDEBUG struct DynStr; diff --git a/source/blender/blenkernel/BKE_dyntopo.hh b/source/blender/blenkernel/BKE_dyntopo.hh new file mode 100644 index 00000000000..6969c4c2286 --- /dev/null +++ b/source/blender/blenkernel/BKE_dyntopo.hh @@ -0,0 +1,171 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup bke + * + * Dynamic remesher for PBVH + */ + +#include "BKE_paint.hh" +#include "BKE_pbvh_api.hh" + +#include "BLI_math_geom.h" +#include "BLI_math_vector.h" +#include "BLI_math_vector_types.hh" + +#define DYNTOPO_CD_INTERP +#define DYNTOPO_DYNAMIC_TESS + +struct SculptSearchSphereData; + +namespace blender::bke::dyntopo { + +float dist_to_tri_sphere_simple(float p[3], float v1[3], float v2[3], float v3[3], float n[3]); + +struct BrushTester { + bool is_sphere_or_tube; + + virtual ~BrushTester() {} + + virtual bool vert_in_range(BMVert * /*v*/) + { + return true; + } + virtual bool tri_in_range(BMVert * /*tri*/[3], float * /*no*/) + { + return true; + } +}; + +struct BrushSphere : public BrushTester { + BrushSphere(float3 center, float radius) + : center_(center), radius_(radius), radius_squared_(radius * radius) + { + is_sphere_or_tube = true; + } + + bool vert_in_range(BMVert *v) override + { + return len_squared_v3v3(center_, v->co) <= radius_squared_; + } + bool tri_in_range(BMVert *tri[3], float *no) override + { + /* Check if triangle intersects the sphere */ + float dis = dist_to_tri_sphere_simple((float *)center_, + (float *)tri[0]->co, + (float *)tri[1]->co, + (float *)tri[2]->co, + (float *)no); + return dis <= radius_squared_; + } + + inline const float3 ¢er() + { + return center_; + } + inline float radius_squared() + { + return radius_squared_; + } + + inline float radius() + { + return radius_; + } + + protected: + float3 center_; + float radius_, radius_squared_; +}; + +struct BrushTube : public BrushSphere { + BrushTube(float3 center, float3 view_normal, float radius) + : BrushSphere(center, radius), view_normal_(view_normal) + { + project_plane_normalized_v3_v3v3(center_proj_, center_, view_normal_); + } + + bool vert_in_range(BMVert *v) override + { + float c[3]; + + project_plane_normalized_v3_v3v3(c, v->co, view_normal_); + + return len_squared_v3v3(center_proj_, c) <= radius_squared_; + } + + bool tri_in_range(BMVert *tri[3], float * /*no*/) override + { + float c[3]; + float tri_proj[3][3]; + + project_plane_normalized_v3_v3v3(tri_proj[0], tri[0]->co, view_normal_); + project_plane_normalized_v3_v3v3(tri_proj[1], tri[1]->co, view_normal_); + project_plane_normalized_v3_v3v3(tri_proj[2], tri[2]->co, view_normal_); + + closest_on_tri_to_point_v3(c, center_proj_, tri_proj[0], tri_proj[1], tri_proj[2]); + + /* Check if triangle intersects the sphere */ + return len_squared_v3v3(center_proj_, c) <= radius_squared_; + } + + private: + float3 center_proj_, view_normal_; +}; + +struct BrushNoRadius : public BrushTester { + BrushNoRadius() + { + is_sphere_or_tube = false; + } +}; + +typedef float (*DyntopoMaskCB)(PBVHVertRef vertex, void *userdata); + +enum PBVHTopologyUpdateMode { + PBVH_None = 0, + PBVH_Subdivide = 1 << 0, + PBVH_Collapse = 1 << 1, + PBVH_Cleanup = 1 << 2, // dissolve verts surrounded by either 3 or 4 triangles then triangulate + PBVH_LocalSubdivide = 1 << 3, + PBVH_LocalCollapse = 1 << 4 +}; +ENUM_OPERATORS(PBVHTopologyUpdateMode, PBVH_LocalCollapse); + +void detail_size_set(PBVH *pbvh, float detail_size, float detail_range); + +bool remesh_topology(blender::bke::dyntopo::BrushTester *brush_tester, + struct Object *ob, + PBVH *pbvh, + PBVHTopologyUpdateMode mode, + bool use_frontface, + blender::float3 view_normal, + bool updatePBVH, + DyntopoMaskCB mask_cb, + void *mask_cb_data, + float quality); + +bool remesh_topology_nodes(blender::bke::dyntopo::BrushTester *tester, + struct Object *ob, + PBVH *pbvh, + bool (*searchcb)(const PBVHNode &node, + const float3 &location, + const float radius_sq, + const bool original), + void (*undopush)(PBVHNode *node, void *data), + const blender::float3 &location, + const float radius_sq, + const bool original, + PBVHTopologyUpdateMode mode, + bool use_frontface, + blender::float3 view_normal, + bool updatePBVH, + DyntopoMaskCB mask_cb, + void *mask_cb_data, + float quality, + void *searchData); + +void after_stroke(PBVH *pbvh, bool force_balance); +} // namespace blender::bke::dyntopo diff --git a/source/blender/blenkernel/BKE_dyntopo_set.hh b/source/blender/blenkernel/BKE_dyntopo_set.hh new file mode 100644 index 00000000000..0950689797a --- /dev/null +++ b/source/blender/blenkernel/BKE_dyntopo_set.hh @@ -0,0 +1,160 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup bli + * + * A simple set class that's optimized for iteration. + * Elements are stored in both a blender::Map and a flat array. + */ + +#include "BLI_compiler_attrs.h" +#include "BLI_map.hh" +#include "BLI_set.hh" +#include "BLI_vector.hh" + +#include + +#include + +namespace blender::bke::dyntopo { +template class DyntopoSet { + public: + DyntopoSet(int64_t reserve) + { + elem_to_index_.reserve(reserve); + index_to_elem_.reserve(reserve); + } + DyntopoSet() {} + DyntopoSet(const DyntopoSet &) = delete; + + template, typename RT = T> struct iterator_base { + iterator_base() : set_(nullptr), i_(-1) {} + iterator_base(DS *set, int i) : set_(set), i_(i) {} + iterator_base(const iterator_base &b) : set_(b.set_), i_(b.i_) {} + + iterator_base &operator=(const iterator_base &b) + { + set_ = b.set_; + i_ = b.i_; + + return *this; + } + + inline RT *operator*() + { + return set_->index_to_elem_[i_]; + } + + inline iterator_base &operator++() + { + i_++; + + while (i_ < set_->index_to_elem_.size() && set_->index_to_elem_[i_] == nullptr) { + i_++; + } + + return *this; + } + + inline bool operator==(const iterator_base &b) + { + return b.i_ == i_; + } + + inline bool operator!=(const iterator_base &b) + { + return b.i_ != i_; + } + + private: + DS *set_; + int i_; + }; + + using iterator = iterator_base, T>; + using const_iterator = iterator_base, const T>; + + bool contains(T *key) + { + return elem_to_index_.contains(key); + } + + void remove(T *key) + { + if (!elem_to_index_.contains(key)) { + return; + } + + int i = elem_to_index_.pop(key); + index_to_elem_[i] = nullptr; + freelist_.append(i); + } + + /* Add key, returns true if key was already in set. */ + bool add(T *key) + { + int i; + if (freelist_.size() > 0) { + i = freelist_.last(); + } + else { + i = index_to_elem_.size(); + } + + bool was_added = elem_to_index_.add(key, i); + if (was_added) { + if (i == index_to_elem_.size()) { + index_to_elem_.append(key); + } + else { + freelist_.pop_last(); + index_to_elem_[i] = key; + } + } + + return was_added; + } + + int size() + { + return elem_to_index_.size(); + } + + iterator begin() + { + int i = 0; + while (i < index_to_elem_.size() && index_to_elem_[i] == nullptr) { + i++; + } + + return iterator(this, i); + } + + iterator end() + { + return iterator(this, index_to_elem_.size()); + } + + const_iterator begin() const + { + int i = 0; + while (i < index_to_elem_.size() && index_to_elem_[i] == nullptr) { + i++; + } + + return const_iterator(this, i); + } + + const_iterator end() const + { + return const_iterator(this, index_to_elem_.size()); + } + + private: + blender::Map elem_to_index_; + blender::Vector index_to_elem_; + blender::Vector freelist_; +}; +} // namespace blender::bke::dyntopo diff --git a/source/blender/blenkernel/BKE_paint.hh b/source/blender/blenkernel/BKE_paint.hh index 4d6963e34b6..da2fb63d5c6 100644 --- a/source/blender/blenkernel/BKE_paint.hh +++ b/source/blender/blenkernel/BKE_paint.hh @@ -15,16 +15,26 @@ #include "BLI_offset_indices.hh" #include "BLI_ordered_edge.hh" #include "BLI_set.hh" +#include "BLI_utildefines.h" #include "DNA_brush_enums.h" +#include "DNA_brush_types.h" #include "DNA_customdata_types.h" #include "DNA_object_enums.h" +#include "DNA_scene_enums.h" #include "BKE_pbvh.hh" +#include "bmesh.hh" + +#include + +struct SculptAttribute; struct BMFace; struct BMLog; struct BMesh; +struct BMIdMap; +struct BMLog; struct BlendDataReader; struct BlendWriter; struct Brush; @@ -230,7 +240,7 @@ bool BKE_paint_always_hide_test(const Object *ob); */ bool paint_is_grid_face_hidden(blender::BoundedBitSpan grid_hidden, int gridsize, int x, int y); /** - * Return true if all vertices in the face are visible, false otherwise. + * Return true if face is visible. */ bool paint_is_bmesh_face_hidden(const BMFace *f); @@ -245,6 +255,7 @@ void BKE_paint_face_set_overlay_color_get(int face_set, int seed, uchar r_color[ bool paint_calculate_rake_rotation(UnifiedPaintSettings *ups, Brush *brush, const float mouse_pos[2], + const float initial_mouse_pos[2], PaintMode paint_mode, bool stroke_has_started); void paint_update_brush_rake_rotation(UnifiedPaintSettings *ups, Brush *brush, float rotation); @@ -335,6 +346,13 @@ struct SculptBoundary { * a distance of 0. */ float *distance; + float (*smoothco)[3]; + float *boundary_dist; // distances from verts to boundary + float (*boundary_tangents)[3]; + + PBVHVertRef *boundary_closest; + int sculpt_totvert; + /* Data for drawing the preview. */ SculptBoundaryPreviewEdge *edges; int edges_capacity; @@ -368,7 +386,7 @@ struct SculptBoundary { /* Bend Deform type. */ struct { float (*pivot_rotation_axis)[3]; - float (*pivot_positions)[3]; + float (*pivot_positions)[4]; } bend; /* Slide Deform type. */ @@ -381,6 +399,14 @@ struct SculptBoundary { blender::float3 rotation_axis; blender::float3 pivot_position; } twist; + + /* Cicrle Deform type. */ + struct { + float (*origin)[3]; + float *radius; + } circle; + + int deform_target; }; struct SculptFakeNeighbors { @@ -390,7 +416,7 @@ struct SculptFakeNeighbors { float current_max_distance; /* Indexed by vertex, stores the vertex index of its fake neighbor if available. */ - int *fake_neighbor_index; + PBVHVertRef *fake_neighbor_index; }; /* Session data (mode-specific) */ @@ -406,6 +432,8 @@ struct SculptAttributeParams { */ int permanent : 1; /* Cannot be combined with simple_array. */ int stroke_only : 1; /* Release layer at end of struct */ + int nointerp : 1; + int nocopy : 1; }; struct SculptAttribute { @@ -438,6 +466,10 @@ struct SculptAttribute { * inside of SculptSession.temp_attribute are used. */ bool used; + bool is_empty() const + { + return !used; + } }; #define SCULPT_MAX_ATTRIBUTES 64 @@ -455,23 +487,65 @@ struct SculptAttribute { /* Convenience pointers for standard sculpt attributes. */ struct SculptAttributePointers { + SculptAttribute *face_set; + /* Persistent base. */ SculptAttribute *persistent_co; SculptAttribute *persistent_no; SculptAttribute *persistent_disp; + /* Layer brush. */ + SculptAttribute *layer_displayment; + /* Precomputed auto-mask factor indexed by vertex, owned by the auto-masking system and * initialized in #auto_mask::cache_init when needed. */ - SculptAttribute *automasking_factor; - SculptAttribute *automasking_occlusion; /* CD_PROP_INT8. */ + SculptAttribute *automasking_factor; /* Stroke only. */ + SculptAttribute *automasking_occlusion; /* CD_PROP_INT8, stroke only */ SculptAttribute *automasking_stroke_id; - SculptAttribute *automasking_cavity; + SculptAttribute *automasking_cavity; /* Stroke only. */ SculptAttribute *topology_island_key; /* CD_PROP_INT8 */ /* BMesh */ SculptAttribute *dyntopo_node_id_vertex; SculptAttribute *dyntopo_node_id_face; + SculptAttribute *rake_temp; + + SculptAttribute *face_areas; + + SculptAttribute *smooth_bdist; + SculptAttribute *smooth_vel; + + /* Sculpt utility attributes. */ + SculptAttribute *stroke_id; + SculptAttribute *boundary_flags; /* CD_PROP_INT32, vert */ + SculptAttribute *edge_boundary_flags; /* CD_PROP_INT32, vert */ + SculptAttribute *valence; /* CD_PROP_INT32, vert */ + SculptAttribute *flags; /* CD_PROP_INT8, vert */ + + SculptAttribute *orig_co, *orig_no; /* CD_PROP_FLOAT3, vert */ + SculptAttribute *orig_fsets; /* CD_PROP_INT32, face */ + SculptAttribute *orig_color; /* CD_PROP_FLOAT4, vert */ + SculptAttribute *orig_mask; /* CD_PROP_FLOAT vert */ + + SculptAttribute *curvature_dir; /* Curvature direction vectors, CD_PROP_FLOAT3 */ + + SculptAttribute *smear_previous; + SculptAttribute *hide_poly; + SculptAttribute *limit_surface; + + SculptAttribute *layer_disp; + SculptAttribute *layer_id; + + SculptAttribute *prefairing_co; + SculptAttribute *fairing_fade; + SculptAttribute *fairing_mask; + + /* Stores the displacement produced by the laplacian step of HC smooth */ + SculptAttribute *laplacian_disp; + + /* Enhance Details */ + SculptAttribute *detail_directions; /* Stroke only. */ }; struct SculptSession { @@ -487,8 +561,14 @@ struct SculptSession { /* These are always assigned to base mesh data when using PBVH_FACES and PBVH_GRIDS. */ blender::MutableSpan vert_positions; + blender::Span edges; blender::OffsetIndices faces; blender::Span corner_verts; + blender::Span corner_edges; + + const int *material_index; + + CustomData *vdata, *edata, *ldata, *pdata; /* These contain the vertex and poly counts of the final mesh. */ int totvert, faces_num; @@ -516,21 +596,43 @@ struct SculptSession { /* Mesh Face Sets */ /* Total number of faces of the base mesh. */ - int totfaces; + int totedges, totloops, totfaces; /* The 0 ID is not used by the tools or the visibility system, it is just used when creating new * geometry (the trim tool, for example) to detect which geometry was just added, so it can be * assigned a valid Face Set after creation. Tools are not intended to run with Face Sets IDs set - * to 0. */ - const int *face_sets; + * to 0. + */ + int *face_sets; /** * A reference to the ".hide_poly" attribute, to store whether (base) faces are hidden. * May be null. */ - const bool *hide_poly; + bool *hide_poly; + + bool *select_poly; /* BMesh for dynamic topology sculpting */ BMesh *bm; + BMIdMap *bm_idmap; + + /* TODO: get rid of these cd_ members and use + * .attrs.XXX.bmesh_cd_offset directly. + */ + int cd_vert_node_offset; + int cd_face_node_offset; + int cd_vcol_offset; + int cd_vert_mask_offset; + int cd_faceset_offset; + int cd_face_areas; + + float *vmask; + + int totuv; + + /* Reproject customdata during smooth. */ + eAttrCorrectMode distort_correction_mode; + /* Undo/redo log for dynamic topology sculpting */ BMLog *bm_log; @@ -539,6 +641,11 @@ struct SculptSession { /* PBVH acceleration structure */ PBVH *pbvh; + PBVH *last_pbvh; + + /* Setting this to true allows a PBVH rebuild when evaluating the object even if the stroke or + * filter caches are active. */ + bool needs_pbvh_rebuild; /* Object is deformed with some modifiers. */ bool deform_modifiers_active; @@ -558,8 +665,8 @@ struct SculptSession { /* Cursor data and active vertex for tools */ PBVHVertRef active_vertex; + PBVHFaceRef active_face; - int active_face_index; int active_grid_index; /* When active, the cursor draws with faded colors, indicating that there is an action @@ -576,6 +683,7 @@ struct SculptSession { * when * the gesture starts (intersection with the surface and if they ray hit the surface or not). */ + blender::float3 gesture_initial_back_location; blender::float3 gesture_initial_location; blender::float3 gesture_initial_normal; bool gesture_initial_hit; @@ -584,6 +692,14 @@ struct SculptSession { RegionView3D *rv3d; View3D *v3d; Scene *scene; + int cd_origvcol_offset; + int cd_origco_offset; + int cd_origno_offset; + + /* Face Sets by topology. */ + int face_set_last_created; + PBVHFaceRef face_set_last_poly; + PBVHEdgeRef face_set_last_edge; /* Dynamic mesh preview */ PBVHVertRef *preview_vert_list; @@ -657,7 +773,7 @@ struct SculptSession { */ bool sticky_shading_color; - uchar stroke_id; + ushort stroke_id; /** * Last used painting canvas key. @@ -665,18 +781,42 @@ struct SculptSession { char *last_paint_canvas_key; blender::float3 last_normal; + /* Used to derive initial tip rotation. */ + float last_grab_delta[3]; + + blender::Span poly_normals; + int last_automasking_settings_hash; uchar last_automask_stroke_id; + bool *sharp_edge; + bool *seam_edge; bool islands_valid; /* Is attrs.topology_island_key valid? */ + + bool hard_edge_mode; + DynTopoSettings cached_dyntopo; + float sharp_angle_limit; + eSculptBoundary smooth_boundary_flag; }; void BKE_sculptsession_free(Object *ob); void BKE_sculptsession_free_deformMats(SculptSession *ss); void BKE_sculptsession_free_vwpaint_data(SculptSession *ss); + void BKE_sculptsession_bm_to_me(Object *ob, bool reorder); void BKE_sculptsession_bm_to_me_for_render(Object *object); int BKE_sculptsession_vertex_count(const SculptSession *ss); +void BKE_sculpt_ensure_idmap(Object *ob); + +void BKE_sculpt_ensure_origco(Object *ob); +void BKE_sculpt_ensure_origmask(Object *ob); +void BKE_sculpt_ensure_origcolor(Object *ob); +void BKE_sculpt_ensure_origfset(Object *ob); +void BKE_sculpt_ensure_curvature_dir(Object *ob); + +/* Ensures Sculpt_flags and sculpt_valence layers. */ +void BKE_sculpt_ensure_sculpt_layers(Object *ob); + /* Ensure an attribute layer exists. */ SculptAttribute *BKE_sculpt_attribute_ensure(Object *ob, blender::bke::AttrDomain domain, @@ -698,10 +838,34 @@ void BKE_sculpt_attribute_destroy_temporary_all(Object *ob); /* Destroy attributes that were marked as stroke only in SculptAttributeParams. */ void BKE_sculpt_attributes_destroy_temporary_stroke(Object *ob); +/* Release a SculptAttribute ref without destroying the underlying attribute. */ +void BKE_sculpt_attribute_release_ref(Object *ob, SculptAttribute *attr); + +SculptAttribute BKE_sculpt_find_attribute(Object *ob, const char *name); + +bool BKE_sculpt_init_flags_valence(Object *ob, PBVH *pbvh, int totvert, bool reset_flags); + +BMesh *BKE_sculptsession_empty_bmesh_create(void); +void BKE_sculptsession_bmesh_attr_update_internal(Object *ob); + +/* Ensures non-temporary attributes in me exist in the sculpt mesh, or vice + * versa if load_to_mesh is true. + */ +void BKE_sculptsession_sync_attributes(Object *ob, Mesh *me, bool load_to_mesh); + +void BKE_sculptsession_bmesh_add_layers(Object *ob); +void BKE_sculptsession_update_attr_refs(Object *ob); + +int BKE_sculptsession_get_totvert(const SculptSession *ss); + +void BKE_sculpt_distort_correction_set(Object *ob, eAttrCorrectMode value); +void BKE_sculptsession_free_attribute_refs(Object *ob); + /** * Create new color layer on object if it doesn't have one and if experimental feature set has * sculpt vertex color enabled. Returns truth if new layer has been added, false otherwise. */ + void BKE_sculpt_color_layer_create_if_needed(Object *object); /** @@ -720,6 +884,7 @@ MultiresModifierData *BKE_sculpt_multires_active(const Scene *scene, Object *ob) * Update the pointer to the ".hide_poly" attribute. This is necessary because it is dynamically * created, removed, and made mutable. */ +bool *BKE_sculpt_hide_poly_ensure(Object *ob); void BKE_sculpt_hide_poly_pointer_update(Object &object); /** @@ -748,6 +913,23 @@ void BKE_sculpt_sync_face_visibility_to_grids(Mesh *mesh, SubdivCCG *subdiv_ccg) */ bool BKE_sculptsession_use_pbvh_draw(const Object *ob, const RegionView3D *rv3d); +char BKE_get_fset_boundary_symflag(Object *object); + +bool BKE_sculpt_has_persistent_base(SculptSession *ss); + +/** + * Sets ob->sculpt->bm. The PBVH will be recreated if it exists + * (if it's of type PBVH_BMESH) as will ob->sculpt->bm_idmap. + * + * Note: BMLog (ob->sculpt->bm_log) doesn't need to be reallocated. + */ +void BKE_sculpt_set_bmesh(Object *ob, BMesh *bm, bool free_existing = true); + +enum { + SCULPT_MASK_LAYER_CALC_VERT = (1 << 0), + SCULPT_MASK_LAYER_CALC_LOOP = (1 << 1), +}; + /* paint_vertex.cc */ /** @@ -779,4 +961,237 @@ bool BKE_paint_canvas_image_get(PaintModeSettings *settings, ImageUser **r_image_user); int BKE_paint_canvas_uvmap_layer_index_get(const PaintModeSettings *settings, Object *ob); void BKE_sculpt_check_cavity_curves(Sculpt *sd); -CurveMapping *BKE_sculpt_default_cavity_curve(); +CurveMapping *BKE_sculpt_default_cavity_curve(void); + +namespace blender::bke::paint { + +/* Base implementation for vertex_attr_*** and face_attr_*** methods. + * Returns a pointer to the attribute data (as defined by attr) for elem. + */ +template +static T *elem_attr_ptr(const ElemRef elem, const SculptAttribute *attr) +{ + void *ptr = nullptr; + + if (attr->data) { + char *p = (char *)attr->data; + int idx = (int)elem.i; + + if (attr->data_for_bmesh) { + BMElem *e = (BMElem *)elem.i; + idx = e->head.index; + } + + ptr = p + attr->elem_size * (int)idx; + } + else { + BMElem *v = (BMElem *)elem.i; + ptr = BM_ELEM_CD_GET_VOID_P(v, attr->bmesh_cd_offset); + } + + return static_cast(ptr); +} + +/* + * Get a pointer to attribute data at vertex. + * + * Example: float *persistent_co = vertex_attr_ptr(vertex, ss->attrs.persistent_co); + */ +template +static T *vertex_attr_ptr(const PBVHVertRef vertex, const SculptAttribute *attr) +{ + return elem_attr_ptr(vertex, attr); +} + +/* + * Get attribute data at vertex. + * + * Example: float weight = vertex_attr_get(vertex, ss->attrs.automasking_factor); + */ +template +static T vertex_attr_get(const PBVHVertRef vertex, const SculptAttribute *attr) +{ + return *vertex_attr_ptr(vertex, attr); +} + +/* + * Set attribute data at vertex. + * + * vertex_attr_set(vertex, ss->attrs.automasking_factor, 1.0f); + */ +template +static void vertex_attr_set(const PBVHVertRef vertex, const SculptAttribute *attr, T data) +{ + *vertex_attr_ptr(vertex, attr) = data; +} + +/* + * Get a pointer to attribute data at vertex. + * + * Example: float *persistent_co = vertex_attr_ptr(vertex, ss->attrs.persistent_co); + */ +template static T *edge_attr_ptr(const PBVHEdgeRef edge, const SculptAttribute *attr) +{ + return elem_attr_ptr(edge, attr); +} +/* + * Get attribute data at vertex. + * + * Example: float weight = vertex_attr_get(vertex, ss->attrs.automasking_factor); + */ +template static T edge_attr_get(const PBVHEdgeRef edge, const SculptAttribute *attr) +{ + return *edge_attr_ptr(edge, attr); +} + +/* + * Set attribute data at vertex. + * + * vertex_attr_set(vertex, ss->attrs.automasking_factor, 1.0f); + */ +template +static void edge_attr_set(const PBVHEdgeRef edge, const SculptAttribute *attr, T data) +{ + *edge_attr_ptr(edge, attr) = data; +} + +template static T *face_attr_ptr(const PBVHFaceRef face, const SculptAttribute *attr) +{ + return elem_attr_ptr(face, attr); +} + +template static T face_attr_get(const PBVHFaceRef face, const SculptAttribute *attr) +{ + return *face_attr_ptr(face, attr); +} + +template +static void face_attr_set(const PBVHFaceRef face, const SculptAttribute *attr, T data) +{ + *face_attr_ptr(face, attr) = data; +} + +bool get_original_vertex(SculptSession *ss, + PBVHVertRef vertex, + float **r_co, + float **r_no, + float **r_color, + float **r_mask); +void load_all_original(Object *ob); +} // namespace blender::bke::paint + +template +inline void BKE_sculpt_boundary_flag_update(SculptSession *ss, + PBVHElemRef elem, + bool flag_vert_edges = false) +{ + int *flags; + + if constexpr (std::is_same_v) { + PBVHVertRef vertex = {elem.i}; + flags = blender::bke::paint::vertex_attr_ptr(elem, ss->attrs.boundary_flags); + + if (flag_vert_edges) { + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_BMESH: { + BMVert *v = reinterpret_cast(vertex.i); + if (!v->e) { + break; + } + + BMEdge *e = v->e; + do { + PBVHEdgeRef edge = {reinterpret_cast(e)}; + *blender::bke::paint::edge_attr_ptr( + edge, ss->attrs.edge_boundary_flags) |= SCULPT_BOUNDARY_NEEDS_UPDATE | + SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE; + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + break; + } + case PBVH_FACES: + /* If we have a vertex to edge map use it. */ + if (!ss->vert_to_edge_map.is_empty()) { + for (int edge_i : ss->vert_to_edge_map[vertex.i]) { + *blender::bke::paint::edge_attr_ptr( + {edge_i}, ss->attrs.edge_boundary_flags) |= SCULPT_BOUNDARY_NEEDS_UPDATE | + SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE; + } + } + else { /* Otherwise use vertex to poly map. */ + for (int poly_i : ss->vert_to_face_map[vertex.i]) { + for (int loop_i : ss->faces[poly_i]) { + if (ss->corner_verts[loop_i] == vertex.i) { + int edge_i = ss->corner_edges[loop_i]; + *blender::bke::paint::edge_attr_ptr( + {edge_i}, + ss->attrs.edge_boundary_flags) |= SCULPT_BOUNDARY_NEEDS_UPDATE | + SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE; + } + } + } + } + break; + case PBVH_GRIDS: + /* Not supported. */ + break; + } + } + } + else { + flags = blender::bke::paint::edge_attr_ptr(elem, ss->attrs.edge_boundary_flags); + } + + *flags |= SCULPT_BOUNDARY_NEEDS_UPDATE | SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE; +} + +template +inline void BKE_sculpt_boundary_flag_uv_update(SculptSession *ss, PBVHElemRef elem) +{ + int *flags; + + if constexpr (std::is_same_v) { + flags = blender::bke::paint::vertex_attr_ptr(elem, ss->attrs.boundary_flags); + } + else { + flags = blender::bke::paint::edge_attr_ptr(elem, ss->attrs.edge_boundary_flags); + } + + *flags |= SCULPT_BOUNDARY_UPDATE_UV; +} + +template +inline void BKE_sculpt_sharp_boundary_flag_update(SculptSession *ss, + PBVHElemRef elem, + bool update_ring = false) +{ + int *flags; + + if constexpr (std::is_same_v) { + flags = blender::bke::paint::vertex_attr_ptr(elem, ss->attrs.boundary_flags); + } + else { + flags = blender::bke::paint::edge_attr_ptr(elem, ss->attrs.edge_boundary_flags); + } + + *flags |= SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE; + + if constexpr (std::is_same_v) { + if (update_ring && ss->bm) { + BMVert *v = reinterpret_cast(elem.i); + if (!v->e) { + return; + } + + BMEdge *e = v->e; + do { + PBVHVertRef vertex2 = {reinterpret_cast(BM_edge_other_vert(e, v))}; + + int *flags2 = blender::bke::paint::vertex_attr_ptr(vertex2, ss->attrs.boundary_flags); + *flags2 |= SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE; + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + } + } +} + +int *BKE_sculpt_face_sets_ensure(Object *ob); diff --git a/source/blender/blenkernel/BKE_pbvh.hh b/source/blender/blenkernel/BKE_pbvh.hh index 8ca52474bbc..f8407144557 100644 --- a/source/blender/blenkernel/BKE_pbvh.hh +++ b/source/blender/blenkernel/BKE_pbvh.hh @@ -14,6 +14,12 @@ struct PBVHNode; struct BMesh; +namespace blender::draw::pbvh { +struct PBVHBatches; +} + +using blender::draw::pbvh::PBVHBatches; + enum PBVHType { PBVH_FACES, PBVH_GRIDS, @@ -39,10 +45,20 @@ enum PBVHNodeFlags { PBVH_UpdateTopology = 1 << 13, PBVH_UpdateColor = 1 << 14, + PBVH_RebuildPixels = 1 << 15, - PBVH_TexLeaf = 1 << 16, - /** Used internally by `pbvh_bmesh.cc`. */ - PBVH_TopologyUpdated = 1 << 17, + PBVH_Delete = 1 << 16, + PBVH_UpdateCurvatureDir = 1 << 17, + PBVH_UpdateTris = 1 << 18, + PBVH_RebuildNodeVerts = 1 << 19, + + /* Tri areas are not guaranteed to be up to date, tools should + * update all nodes on first step of brush. + */ + PBVH_UpdateTriAreas = 1 << 20, + PBVH_UpdateOtherVerts = 1 << 21, + PBVH_TexLeaf = 1 << 22, + PBVH_TopologyUpdated = 1 << 23, /* Used internally by dyntopo.c. */ }; ENUM_OPERATORS(PBVHNodeFlags, PBVH_TopologyUpdated); @@ -63,6 +79,20 @@ struct PBVHVertRef { PBVH_REF_CXX_METHODS(PBVHVertRef) }; +/* NOTE: edges in PBVH_GRIDS are always pulled from the base mesh. */ +struct PBVHEdgeRef { + intptr_t i; + + PBVH_REF_CXX_METHODS(PBVHVertRef) +}; + +/* NOTE: faces in PBVH_GRIDS are always puled from the base mesh. */ +struct PBVHFaceRef { + intptr_t i; + + PBVH_REF_CXX_METHODS(PBVHVertRef) +}; + #define PBVH_REF_NONE -1LL /* Public members of PBVH, used for inlined functions. */ diff --git a/source/blender/blenkernel/BKE_pbvh_api.hh b/source/blender/blenkernel/BKE_pbvh_api.hh index bf0a9fb4abc..001174efb6f 100644 --- a/source/blender/blenkernel/BKE_pbvh_api.hh +++ b/source/blender/blenkernel/BKE_pbvh_api.hh @@ -9,57 +9,100 @@ * \brief A BVH for high poly meshes. */ +#include "BKE_attribute.h" +#include "BKE_attribute.hh" +#include "BKE_dyntopo_set.hh" + #include #include #include "BLI_bit_group_vector.hh" +#include "BLI_bit_vector.hh" +#include "BLI_bounds.hh" #include "BLI_bounds_types.hh" #include "BLI_compiler_compat.h" #include "BLI_function_ref.hh" -#include "BLI_index_mask.hh" #include "BLI_math_vector_types.hh" #include "BLI_offset_indices.hh" #include "BLI_span.hh" #include "BLI_vector.hh" +#include "DNA_brush_enums.h" /* for eAttrCorrectMode */ #include "DNA_customdata_types.h" /* For embedding CCGKey in iterator. */ +#include "BKE_attribute.h" #include "BKE_ccg.h" #include "BKE_pbvh.hh" #include "bmesh.hh" +#include "bmesh_log.hh" -struct BMLog; -struct BMesh; +#include + +struct Object; +struct Scene; struct CCGElem; struct CCGKey; struct CustomData; +struct DMFlagMat; struct IsectRayPrecalc; +struct MLoopTri; struct Mesh; struct PBVH; struct PBVHNode; +struct SculptSession; struct SubdivCCG; +struct TaskParallelSettings; struct Image; struct ImageUser; -namespace blender { -namespace bke { -enum class AttrDomain : int8_t; -} -namespace draw::pbvh { -struct PBVHBatches; + +namespace blender::draw::pbvh { struct PBVH_GPU_Args; -} // namespace draw::pbvh -} // namespace blender +} + +using blender::bke::AttrDomain; +using blender::draw::pbvh::PBVH_GPU_Args; + +struct PBVHTri { + int v[3]; /* References into PBVHTriBuf->verts. */ + int eflag; /* Bitmask of which edges in the tri are real edges in the mesh. */ + intptr_t l[3]; /* Loops, currently just BMLoop pointers for now. */ + PBVHFaceRef f; + float no[3]; +}; + +struct PBVHTriBuf { + blender::Vector tris; + blender::Vector verts; + blender::Vector edges; + blender::Vector loops; + + int mat_nr = 0; + + float min[3], max[3]; +}; + +/* + * These structs represent logical verts/edges/faces. + * for PBVH_GRIDS and PBVH_FACES they store integer + * offsets, PBVH_BMESH stores pointers. + * + * The idea is to enforce stronger type checking by encapsulating + * intptr_t's in structs. + */ + +/* A generic PBVH vertex. + * + * NOTE: in PBVH_GRIDS we consider the final grid points + * to be vertices. This is not true of edges or faces which are pulled from + * the base mesh. + */ struct PBVHProxyNode { blender::Vector co; }; -struct PBVHColorBufferNode { - float (*color)[4] = nullptr; -}; - struct PBVHPixels { /** * Storage for texture painting on PBVH level. @@ -78,6 +121,16 @@ struct PBVHPixelsNode { void *node_data = nullptr; }; +class PBVHAttrReq { + public: + PBVHAttrReq() = default; + PBVHAttrReq(const AttrDomain domain, const eCustomDataType type) : domain(domain), type(type) {} + + std::string name; + AttrDomain domain; + eCustomDataType type; +}; + struct PBVHFrustumPlanes { float (*planes)[4]; int num_planes; @@ -96,6 +149,18 @@ BLI_INLINE PBVHVertRef BKE_pbvh_make_vref(intptr_t i) return ret; } +BLI_INLINE PBVHEdgeRef BKE_pbvh_make_eref(intptr_t i) +{ + PBVHEdgeRef ret = {i}; + return ret; +} + +BLI_INLINE PBVHFaceRef BKE_pbvh_make_fref(intptr_t i) +{ + PBVHFaceRef ret = {i}; + return ret; +} + BLI_INLINE int BKE_pbvh_vertex_to_index(PBVH *pbvh, PBVHVertRef v) { return (BKE_pbvh_type(pbvh) == PBVH_BMESH && v.i != PBVH_REF_NONE ? @@ -116,25 +181,106 @@ BLI_INLINE PBVHVertRef BKE_pbvh_index_to_vertex(PBVH *pbvh, int index) return BKE_pbvh_make_vref(PBVH_REF_NONE); } +BLI_INLINE int BKE_pbvh_edge_to_index(PBVH *pbvh, PBVHEdgeRef e) +{ + return (BKE_pbvh_type(pbvh) == PBVH_BMESH && e.i != PBVH_REF_NONE ? + BM_elem_index_get((BMEdge *)(e.i)) : + (e.i)); +} + +BLI_INLINE PBVHEdgeRef BKE_pbvh_index_to_edge(PBVH *pbvh, int index) +{ + switch (BKE_pbvh_type(pbvh)) { + case PBVH_FACES: + case PBVH_GRIDS: + return BKE_pbvh_make_eref(index); + case PBVH_BMESH: + return BKE_pbvh_make_eref((intptr_t)BKE_pbvh_get_bmesh(pbvh)->etable[index]); + } + + return BKE_pbvh_make_eref(PBVH_REF_NONE); +} + +BLI_INLINE int BKE_pbvh_face_to_index(PBVH *pbvh, PBVHFaceRef f) +{ + return (BKE_pbvh_type(pbvh) == PBVH_BMESH && f.i != PBVH_REF_NONE ? + BM_elem_index_get((BMFace *)(f.i)) : + (f.i)); +} + +BLI_INLINE PBVHFaceRef BKE_pbvh_index_to_face(PBVH *pbvh, int index) +{ + switch (BKE_pbvh_type(pbvh)) { + case PBVH_FACES: + case PBVH_GRIDS: + return BKE_pbvh_make_fref(index); + case PBVH_BMESH: + return BKE_pbvh_make_fref((intptr_t)BKE_pbvh_get_bmesh(pbvh)->ftable[index]); + } + + return BKE_pbvh_make_fref(PBVH_REF_NONE); +} + /* Callbacks */ -namespace blender::bke::pbvh { +/** + * Returns true if the search should continue from this node, false otherwise. + */ + +typedef void (*BKE_pbvh_HitCallback)(PBVHNode &node, void *data); +typedef void (*BKE_pbvh_HitOccludedCallback)(PBVHNode &node, void *data, float *tmin); + +typedef void (*BKE_pbvh_SearchNearestCallback)(PBVHNode &node, void *data, float *tmin); + +PBVHNode *BKE_pbvh_get_node(PBVH *pbvh, int node); + +/* Building */ + +PBVH *BKE_pbvh_new(PBVHType type); /** * Do a full rebuild with on Mesh data structure. */ -PBVH *build_mesh(Mesh *mesh); -void update_mesh_pointers(PBVH *pbvh, Mesh *mesh); -/** - * Do a full rebuild with on Grids data structure. - */ +void BKE_pbvh_build_mesh(PBVH *pbvh, Mesh *mesh); +void BKE_pbvh_update_mesh_pointers(PBVH *pbvh, Mesh *mesh); + +namespace blender::bke::pbvh { PBVH *build_grids(const CCGKey *key, Mesh *mesh, SubdivCCG *subdiv_ccg); +PBVH *build_mesh(Mesh *mesh); + /** * Build a PBVH from a BMesh. */ -PBVH *build_bmesh(BMesh *bm, BMLog *log, int cd_vert_node_offset, int cd_face_node_offset); +void build_bmesh(PBVH *pbvh, + Mesh *me, + BMesh *bm, + BMLog *log, + BMIdMap *idmap, + const int cd_vert_node_offset, + const int cd_face_node_offset, + const int cd_face_areas, + const int cd_boundary_flag, + const int cd_edge_boundary, + const int cd_flag, + const int cd_valence, + const int cd_origco, + const int cd_origno); -void update_bmesh_offsets(PBVH *pbvh, int cd_vert_node_offset, int cd_face_node_offset); +void set_idmap(PBVH *pbvh, BMIdMap *idmap); + +void update_offsets(PBVH *pbvh, + const int cd_vert_node_offset, + const int cd_face_node_offset, + const int cd_face_areas, + const int cd_boudnary_flags, + const int cd_edge_boundary, + const int cd_flag, + const int cd_valence, + const int cd_origco, + const int cd_origno, + const int cd_curvature_dir); + +float bmesh_detail_size_avg_get(PBVH *pbvh); void build_pixels(PBVH *pbvh, Mesh *mesh, Image *image, ImageUser *image_user); void free(PBVH *pbvh); @@ -154,31 +300,52 @@ void search_callback(PBVH &pbvh, * hit first */ void raycast(PBVH *pbvh, - FunctionRef cb, + const FunctionRef cb, const float ray_start[3], const float ray_normal[3], - bool original); + bool original, + int stroke_id); -bool raycast_node(PBVH *pbvh, +bool raycast_node(SculptSession *ss, + PBVH *pbvh, PBVHNode *node, float (*origco)[3], bool use_origco, - Span corner_verts, - Span hide_poly, + const Span corner_verts, + const Span hide_poly, const float ray_start[3], const float ray_normal[3], IsectRayPrecalc *isect_precalc, + int *hit_count, float *depth, - PBVHVertRef *active_vertex, - int *active_face_grid_index, - float *face_normal); + PBVHVertRef *active_vertex_index, + PBVHFaceRef *active_face_grid_index, + float *face_normal, + int stroke_id); +} // namespace blender::bke::pbvh -bool bmesh_node_raycast_detail(PBVHNode *node, - const float ray_start[3], - IsectRayPrecalc *isect_precalc, - float *depth, - float *r_edge_length); +void BKE_pbvh_set_bm_log(PBVH *pbvh, BMLog *log); +BMLog *BKE_pbvh_get_bm_log(PBVH *pbvh); +/** +checks if original data needs to be updated for v, and if so updates it. Stroke_id +is provided by the sculpt code and is used to detect updates. The reason we do it +inside the verts and not in the nodes is to allow splitting of the pbvh during the stroke. +*/ +bool BKE_pbvh_bmesh_check_origdata(SculptSession *ss, BMVert *v, int stroke_id); + +/** used so pbvh can differentiate between different strokes, + see BKE_pbvh_bmesh_check_origdata */ +void BKE_pbvh_set_stroke_id(PBVH *pbvh, int stroke_id); + +bool BKE_pbvh_bmesh_node_raycast_detail(PBVH *pbvh, + PBVHNode *node, + const float ray_start[3], + IsectRayPrecalc *isect_precalc, + float *depth, + float *r_edge_length); + +namespace blender::bke::pbvh { /** * For orthographic cameras, project the far away ray segment points to the root node so * we can have better precision. @@ -198,89 +365,86 @@ void find_nearest_to_ray(PBVH *pbvh, const float ray_normal[3], bool original); -bool find_nearest_to_ray_node(PBVH *pbvh, +bool find_nearest_to_ray_node(SculptSession *ss, + PBVH *pbvh, PBVHNode *node, float (*origco)[3], bool use_origco, - Span corner_verts, - Span hide_poly, const float ray_start[3], const float ray_normal[3], float *depth, - float *dist_sq); - + float *dist_sq, + int stroke_id); /* Drawing */ void set_frustum_planes(PBVH *pbvh, PBVHFrustumPlanes *planes); -void get_frustum_planes(const PBVH *pbvh, PBVHFrustumPlanes *planes); +void get_frustum_planes(PBVH *pbvh, PBVHFrustumPlanes *planes); void draw_cb(const Mesh &mesh, PBVH *pbvh, bool update_only_visible, - const PBVHFrustumPlanes &update_frustum, - const PBVHFrustumPlanes &draw_frustum, - FunctionRef draw_fn); + PBVHFrustumPlanes &update_frustum, + PBVHFrustumPlanes &draw_frustum, + const FunctionRef draw_fn); } // namespace blender::bke::pbvh +/* PBVH Access */ + +bool BKE_pbvh_has_faces(const PBVH *pbvh); + /** * Get the PBVH root's bounding box. */ blender::Bounds BKE_pbvh_bounding_box(const PBVH *pbvh); -void BKE_pbvh_sync_visibility_from_verts(PBVH *pbvh, Mesh *mesh); +/** + * Multi-res hidden data, only valid for type == PBVH_GRIDS. + */ +blender::BitGroupVector<> *BKE_pbvh_grid_hidden(const PBVH *pbvh); + +void BKE_pbvh_sync_visibility_from_verts(PBVH *pbvh, Mesh *me); namespace blender::bke::pbvh { - -/** - * Returns the number of visible quads in the nodes' grids. - */ int count_grid_quads(const BitGroupVector<> &grid_visibility, Span grid_indices, int gridsize, int display_gridsize); -} // namespace blender::bke::pbvh +} /** * Multi-res level, only valid for type == #PBVH_GRIDS. */ const CCGKey *BKE_pbvh_get_grid_key(const PBVH *pbvh); +CCGElem **BKE_pbvh_get_grids(const PBVH *pbvh); +blender::BitGroupVector<> *BKE_pbvh_get_grid_visibility(const PBVH *pbvh); int BKE_pbvh_get_grid_num_verts(const PBVH *pbvh); int BKE_pbvh_get_grid_num_faces(const PBVH *pbvh); -/** - * Only valid for type == #PBVH_BMESH. - */ -void BKE_pbvh_bmesh_detail_size_set(PBVH *pbvh, float detail_size); - -enum PBVHTopologyUpdateMode { - PBVH_Subdivide = 1, - PBVH_Collapse = 2, -}; -ENUM_OPERATORS(PBVHTopologyUpdateMode, PBVH_Collapse); - -namespace blender::bke::pbvh { - -/** - * Collapse short edges, subdivide long edges. - */ -bool bmesh_update_topology(PBVH *pbvh, - PBVHTopologyUpdateMode mode, - const float center[3], - const float view_normal[3], - float radius, - bool use_frontface, - bool use_projected); - -} // namespace blender::bke::pbvh - /* Node Access */ +void BKE_pbvh_check_tri_areas(PBVH *pbvh, PBVHNode *node); +void BKE_pbvh_face_areas_begin(Object *ob, PBVH *pbvh); +void BKE_pbvh_face_areas_swap_buffers(Object *ob, PBVH *pbvh); + +bool BKE_pbvh_bmesh_check_valence(PBVH *pbvh, PBVHVertRef vertex); +void BKE_pbvh_bmesh_update_valence(PBVH *pbvh, PBVHVertRef vertex); +void BKE_pbvh_bmesh_update_all_valence(PBVH *pbvh); +bool BKE_pbvh_bmesh_mark_update_valence(PBVH *pbvh, PBVHVertRef vertex); + +/* if pbvh uses a split index buffer, will call BKE_pbvh_vert_tag_update_normal_triangulation; + otherwise does nothing. returns true if BKE_pbvh_vert_tag_update_normal_triangulation was + called.*/ +void BKE_pbvh_vert_tag_update_normal_triangulation(PBVHNode *node); +void BKE_pbvh_node_mark_original_update(PBVHNode *node); +void BKE_pbvh_vert_tag_update_normal_tri_area(PBVHNode *node); +void BKE_pbvh_update_all_tri_areas(PBVH *pbvh); void BKE_pbvh_node_mark_update(PBVHNode *node); void BKE_pbvh_node_mark_update_mask(PBVHNode *node); void BKE_pbvh_node_mark_update_color(PBVHNode *node); +void BKE_pbvh_vert_tag_update_normal_visibility(PBVHNode *node); void BKE_pbvh_node_mark_update_face_sets(PBVHNode *node); void BKE_pbvh_node_mark_update_visibility(PBVHNode *node); void BKE_pbvh_node_mark_rebuild_draw(PBVHNode *node); @@ -292,12 +456,20 @@ bool BKE_pbvh_node_fully_hidden_get(const PBVHNode *node); void BKE_pbvh_node_fully_masked_set(PBVHNode *node, int fully_masked); bool BKE_pbvh_node_fully_masked_get(const PBVHNode *node); void BKE_pbvh_node_fully_unmasked_set(PBVHNode *node, int fully_masked); -bool BKE_pbvh_node_fully_unmasked_get(const PBVHNode *node); +bool BKE_pbvh_node_fully_unmasked_get(PBVHNode *node); +void BKE_pbvh_node_mark_curvature_update(PBVHNode *node); void BKE_pbvh_mark_rebuild_pixels(PBVH *pbvh); blender::Span BKE_pbvh_node_get_grid_indices(const PBVHNode &node); +void BKE_pbvh_node_get_grids(PBVH *pbvh, + PBVHNode *node, + const int **grid_indices, + int *totgrid, + int *maxgrid, + int *gridsize, + CCGElem ***r_griddata); int BKE_pbvh_node_num_unique_verts(const PBVH &pbvh, const PBVHNode &node); blender::Span BKE_pbvh_node_get_vert_indices(const PBVHNode *node); blender::Span BKE_pbvh_node_get_unique_vert_indices(const PBVHNode *node); @@ -319,51 +491,59 @@ Span node_face_indices_calc_grids(const PBVH &pbvh, const PBVHNode &node, V } // namespace blender::bke::pbvh +blender::Vector BKE_pbvh_node_calc_face_indices(const PBVH &pbvh, const PBVHNode &node); + blender::Bounds BKE_pbvh_node_get_BB(const PBVHNode *node); blender::Bounds BKE_pbvh_node_get_original_BB(const PBVHNode *node); -float BKE_pbvh_node_get_tmin(const PBVHNode *node); +float BKE_pbvh_node_get_tmin(PBVHNode *node); /** * Test if AABB is at least partially inside the #PBVHFrustumPlanes volume. */ -bool BKE_pbvh_node_frustum_contain_AABB(const PBVHNode *node, const PBVHFrustumPlanes *frustum); +bool BKE_pbvh_node_frustum_contain_AABB(PBVHNode *node, PBVHFrustumPlanes *frustum); /** * Test if AABB is at least partially outside the #PBVHFrustumPlanes volume. */ -bool BKE_pbvh_node_frustum_exclude_AABB(const PBVHNode *node, const PBVHFrustumPlanes *frustum); +bool BKE_pbvh_node_frustum_exclude_AABB(PBVHNode *node, PBVHFrustumPlanes *frustum); -const blender::Set &BKE_pbvh_bmesh_node_unique_verts(PBVHNode *node); -const blender::Set &BKE_pbvh_bmesh_node_other_verts(PBVHNode *node); -const blender::Set &BKE_pbvh_bmesh_node_faces(PBVHNode *node); +blender::bke::dyntopo::DyntopoSet &BKE_pbvh_bmesh_node_unique_verts(PBVHNode *node); +blender::bke::dyntopo::DyntopoSet &BKE_pbvh_bmesh_node_other_verts(PBVHNode *node); +blender::bke::dyntopo::DyntopoSet &BKE_pbvh_bmesh_node_faces(PBVHNode *node); + +void BKE_pbvh_bmesh_regen_node_verts(PBVH *pbvh, bool report); +void BKE_pbvh_bmesh_mark_node_regen(PBVH *pbvh, PBVHNode *node); -/** - * In order to perform operations on the original node coordinates - * (currently just ray-cast), store the node's triangles and vertices. - * - * Skips triangles that are hidden. - */ -void BKE_pbvh_bmesh_node_save_orig(BMesh *bm, BMLog *log, PBVHNode *node, bool use_original); void BKE_pbvh_bmesh_after_stroke(PBVH *pbvh); +/* Update Bounding Box/Redraw and clear flags. */ + namespace blender::bke::pbvh { void update_bounds(PBVH &pbvh, int flags); + void update_mask(PBVH &pbvh); +void update_vertex_data(PBVH &pbvh, int flags); void update_visibility(PBVH &pbvh); void update_normals(PBVH &pbvh, SubdivCCG *subdiv_ccg); -} // namespace blender::bke::pbvh +} // namespace blender::bke::pbvh blender::Bounds BKE_pbvh_redraw_BB(PBVH *pbvh); -namespace blender::bke::pbvh { -IndexMask nodes_to_face_selection_grids(const SubdivCCG &subdiv_ccg, - Span nodes, - IndexMaskMemory &memory); -} + +void BKE_pbvh_get_grid_updates(PBVH *pbvh, bool clear, void ***r_gridfaces, int *r_totface); void BKE_pbvh_grids_update(PBVH *pbvh, const CCGKey *key); -void BKE_pbvh_subdiv_cgg_set(PBVH *pbvh, SubdivCCG *subdiv_ccg); +void BKE_pbvh_subdiv_ccg_set(PBVH *pbvh, SubdivCCG *subdiv_ccg); +void BKE_pbvh_face_sets_set(PBVH *pbvh, int *face_sets); + +/** + * If an operation causes the hide status stored in the mesh to change, this must be called + * to update the references to those attributes, since they are only added when necessary. + */ +void BKE_pbvh_update_hide_attributes_from_mesh(PBVH *pbvh); + +/* Vertex Deformer. */ void BKE_pbvh_vert_coords_apply(PBVH *pbvh, blender::Span vert_positions); -bool BKE_pbvh_is_deformed(const PBVH *pbvh); +bool BKE_pbvh_is_deformed(PBVH *pbvh); /* Vertex Iterator. */ @@ -389,10 +569,10 @@ struct PBVHVertexIter { /* grid */ CCGKey key; - CCGElem *const *grids; + CCGElem **grids; CCGElem *grid; - const blender::BitGroupVector<> *grid_hidden; - std::optional gh; + blender::BitGroupVector<> *grid_hidden; + blender::BoundedBitSpan gh; const int *grid_indices; int totgrid; int gridsize; @@ -407,12 +587,14 @@ struct PBVHVertexIter { bool is_mesh; /* bmesh */ - std::optional::Iterator> bm_unique_verts; - std::optional::Iterator> bm_unique_verts_end; - std::optional::Iterator> bm_other_verts; - std::optional::Iterator> bm_other_verts_end; + int bi; + int bm_cur_set; + blender::bke::dyntopo::DyntopoSet *bm_unique_verts, *bm_other_verts; + blender::bke::dyntopo::DyntopoSet::iterator bm_iter, bm_iter_end; + CustomData *bm_vdata; int cd_vert_mask_offset; + int cd_vcol_offset; /* result: these are all computed in the macro, but we assume * that compiler optimization's will skip the ones we don't use */ @@ -436,12 +618,7 @@ void pbvh_vertex_iter_init(PBVH *pbvh, PBVHNode *node, PBVHVertexIter *vi, int m vi.index = vi.vertex.i = vi.grid_indices[vi.g] * vi.key.grid_area - 1; \ vi.grid = CCG_elem_offset(&vi.key, vi.grids[vi.grid_indices[vi.g]], -1); \ if (mode == PBVH_ITER_UNIQUE) { \ - if (vi.grid_hidden) { \ - vi.gh.emplace((*vi.grid_hidden)[vi.grid_indices[vi.g]]); \ - } \ - else { \ - vi.gh.reset(); \ - } \ + vi.gh = (*vi.grid_hidden)[vi.grid_indices[vi.g]]; \ } \ } \ else { \ @@ -459,8 +636,8 @@ void pbvh_vertex_iter_init(PBVH *pbvh, PBVHNode *node, PBVHVertexIter *vi, int m vi.index++; \ vi.vertex.i++; \ vi.visible = true; \ - if (vi.gh) { \ - if ((*vi.gh)[vi.gy * vi.gridsize + vi.gx]) { \ + if (!vi.gh.is_empty()) { \ + if (vi.gh[vi.gy * vi.gridsize + vi.gx]) { \ continue; \ } \ } \ @@ -476,14 +653,24 @@ void pbvh_vertex_iter_init(PBVH *pbvh, PBVHNode *node, PBVHVertexIter *vi, int m vi.mask = vi.vmask ? vi.vmask[vi.index] : 0.0f; \ } \ else { \ - if (*vi.bm_unique_verts != *vi.bm_unique_verts_end) { \ - vi.bm_vert = **vi.bm_unique_verts; \ - (*vi.bm_unique_verts)++; \ - } \ - else { \ - vi.bm_vert = **vi.bm_other_verts; \ - (*vi.bm_other_verts)++; \ + if (vi.bm_iter == vi.bm_iter_end) { \ + if (vi.bm_cur_set == 0 && mode == PBVH_ITER_ALL) { \ + vi.bm_cur_set = 1; \ + vi.bm_iter = vi.bm_other_verts->begin(); \ + vi.bm_iter_end = vi.bm_other_verts->end(); \ + if (vi.bm_iter == vi.bm_iter_end) { \ + continue; \ + } \ + } \ + else { \ + continue; \ + } \ } \ + BMVert *bv = *vi.bm_iter; \ + ++vi.bm_iter; \ + vi.bm_vert = bv; \ + vi.vertex.i = (intptr_t)bv; \ + vi.index = BM_elem_index_get(vi.bm_vert); \ vi.visible = !BM_elem_flag_test_bool(vi.bm_vert, BM_ELEM_HIDDEN); \ if (mode == PBVH_ITER_UNIQUE && !vi.visible) { \ continue; \ @@ -503,42 +690,102 @@ void pbvh_vertex_iter_init(PBVH *pbvh, PBVHNode *node, PBVHVertexIter *vi, int m #define PBVH_FACE_ITER_VERTS_RESERVED 8 +struct PBVHFaceIter { + PBVHFaceRef face; + int index; + bool *hide; + int *face_set; + int i; + + PBVHVertRef *verts; + int verts_num; + + PBVHVertRef verts_reserved_[PBVH_FACE_ITER_VERTS_RESERVED]; + const PBVHNode *node_; + PBVHType pbvh_type_; + int verts_size_; + blender::bke::dyntopo::DyntopoSet::iterator bm_iter_, bm_iter_end_; + int cd_face_set_; + bool *hide_poly_; + int *face_sets_; + blender::OffsetIndices face_offsets_; + blender::Span looptri_faces_; + blender::Span corner_verts_; + int prim_index_; + const SubdivCCG *subdiv_ccg_; + const BMesh *bm; + CCGKey subdiv_key_; + + int last_face_index_; +}; + +void BKE_pbvh_face_iter_init(PBVH *pbvh, PBVHNode *node, PBVHFaceIter *fd); +void BKE_pbvh_face_iter_step(PBVHFaceIter *fd); +bool BKE_pbvh_face_iter_done(PBVHFaceIter *fd); +void BKE_pbvh_face_iter_finish(PBVHFaceIter *fd); + +/** + * Iterate over faces inside a #PBVHNode. These are either base mesh faces + * (for PBVH_FACES and PBVH_GRIDS) or BMesh faces (for PBVH_BMESH). + */ +#define BKE_pbvh_face_iter_begin(pbvh, node, fd) \ + BKE_pbvh_face_iter_init(pbvh, node, &fd); \ + for (; !BKE_pbvh_face_iter_done(&fd); BKE_pbvh_face_iter_step(&fd)) { + +#define BKE_pbvh_face_iter_end(fd) \ + } \ + BKE_pbvh_face_iter_finish(&fd) + blender::MutableSpan BKE_pbvh_node_get_proxies(PBVHNode *node); void BKE_pbvh_node_free_proxies(PBVHNode *node); PBVHProxyNode &BKE_pbvh_node_add_proxy(PBVH &pbvh, PBVHNode &node); -void BKE_pbvh_node_get_bm_orco_data(PBVHNode *node, - int (**r_orco_tris)[3], - int *r_orco_tris_num, - float (**r_orco_coords)[3], - BMVert ***r_orco_verts); + +// void BKE_pbvh_node_BB_reset(PBVHNode *node); +// void BKE_pbvh_node_BB_expand(PBVHNode *node, float co[3]); bool pbvh_has_mask(const PBVH *pbvh); bool pbvh_has_face_sets(PBVH *pbvh); -blender::Span BKE_pbvh_get_vert_positions(const PBVH *pbvh); -blender::MutableSpan BKE_pbvh_get_vert_positions(PBVH *pbvh); -blender::Span BKE_pbvh_get_vert_normals(const PBVH *pbvh); +void pbvh_show_mask_set(PBVH *pbvh, bool show_mask); +void pbvh_show_face_sets_set(PBVH *pbvh, bool show_face_sets); -PBVHColorBufferNode *BKE_pbvh_node_color_buffer_get(PBVHNode *node); -void BKE_pbvh_node_color_buffer_free(PBVH *pbvh); -bool BKE_pbvh_get_color_layer(Mesh *mesh, +/* Parallelization. */ + +void BKE_pbvh_parallel_range_settings(TaskParallelSettings *settings, + bool use_threading, + int totnode); + +blender::MutableSpan BKE_pbvh_get_vert_positions(const PBVH *pbvh); +blender::Span BKE_pbvh_get_vert_normals(const PBVH *pbvh); +const bool *BKE_pbvh_get_vert_hide(const PBVH *pbvh); +bool *BKE_pbvh_get_vert_hide_for_write(PBVH *pbvh); + +const bool *BKE_pbvh_get_poly_hide(const PBVH *pbvh); + +/* Get active color attribute; if pbvh is non-null + * and is of type PBVH_BMESH the layer inside of + * pbvh->header.bm will be returned, otherwise the + * layer will be looked up inside of me. + */ +bool BKE_pbvh_get_color_layer(PBVH *pbvh, + Mesh *me, CustomDataLayer **r_layer, - blender::bke::AttrDomain *r_domain); + AttrDomain *r_domain); /* Swaps colors at each element in indices (of domain pbvh->vcol_domain) - * with values in colors. */ + * with values in colors. PBVH_FACES only.*/ void BKE_pbvh_swap_colors(PBVH *pbvh, blender::Span indices, blender::MutableSpan r_colors); /* Stores colors from the elements in indices (of domain pbvh->vcol_domain) - * into colors. */ + * into colors. PBVH_FACES only.*/ void BKE_pbvh_store_colors(PBVH *pbvh, blender::Span indices, blender::MutableSpan r_colors); -/* Like BKE_pbvh_store_colors but handles loop->vert conversion */ +/* Like BKE_pbvh_store_colors but handles loop->vert conversion. PBVH_FACES only. */ void BKE_pbvh_store_colors_vertex(PBVH *pbvh, blender::Span indices, blender::MutableSpan r_colors); @@ -551,26 +798,186 @@ void BKE_pbvh_vertex_color_set(PBVH *pbvh, PBVHVertRef vertex, const float color void BKE_pbvh_vertex_color_get(const PBVH *pbvh, PBVHVertRef vertex, float r_color[4]); void BKE_pbvh_ensure_node_loops(PBVH *pbvh); +bool BKE_pbvh_draw_cache_invalid(const PBVH *pbvh); int BKE_pbvh_debug_draw_gen_get(PBVHNode *node); -void BKE_pbvh_pmap_set(PBVH *pbvh, blender::GroupedSpan vert_to_face_map); +int BKE_pbvh_get_node_index(PBVH *pbvh, PBVHNode *node); +int BKE_pbvh_get_node_id(PBVH *pbvh, PBVHNode *node); + +void BKE_pbvh_curvature_update_set(PBVHNode *node, bool state); +bool BKE_pbvh_curvature_update_get(PBVHNode *node); + +int BKE_pbvh_get_totnodes(PBVH *pbvh); + +bool BKE_pbvh_bmesh_check_tris(PBVH *pbvh, PBVHNode *node); +PBVHTriBuf *BKE_pbvh_bmesh_get_tris(PBVH *pbvh, PBVHNode *node); +void BKE_pbvh_bmesh_free_tris(PBVH *pbvh, PBVHNode *node); + +/*recalculates boundary flags for *all* vertices. used by + symmetrize.*/ +void BKE_pbvh_recalc_bmesh_boundary(PBVH *pbvh); +void BKE_pbvh_set_boundary_flags(PBVH *pbvh, int *boundary_flags); + +void BKE_pbvh_bmesh_remove_face(PBVH *pbvh, BMFace *f, bool log_face); +void BKE_pbvh_bmesh_remove_edge(PBVH *pbvh, BMEdge *e, bool log_edge); +void BKE_pbvh_bmesh_remove_vertex(PBVH *pbvh, BMVert *v, bool log_vert); +void BKE_pbvh_bmesh_add_face(PBVH *pbvh, BMFace *f, bool log_face, bool force_tree_walk); + +/* e_tri and f_example are allowed to be nullptr. */ +BMFace *BKE_pbvh_face_create_bmesh(PBVH *pbvh, + BMVert *v_tri[3], + BMEdge *e_tri[3], + const BMFace *f_example); + +/* If node is nullptr then one will be found in the pbvh. */ +BMVert *BKE_pbvh_vert_create_bmesh( + PBVH *pbvh, float co[3], float no[3], PBVHNode *node, BMVert *v_example); +PBVHNode *BKE_pbvh_node_from_face_bmesh(PBVH *pbvh, BMFace *f); +PBVHNode *BKE_pbvh_node_from_index(PBVH *pbvh, int node_i); + +PBVHNode *BKE_pbvh_get_node_leaf_safe(PBVH *pbvh, int i); + +void BKE_pbvh_get_vert_face_areas(PBVH *pbvh, PBVHVertRef vertex, float *r_areas, int valence); +void BKE_pbvh_set_symmetry(PBVH *pbvh, int symmetry, int boundary_symmetry); + +int BKE_pbvh_do_fset_symmetry(int fset, const int symflag, const float *co); + +/* +Uses pmap to build an array of edge indices surrounding vertex +r_edges, r_edges_size, heap_alloc define an existing array to put data in. + +final array is similarly put in these pointers. note that calling code +may pass a stack allocated array (*heap_alloc should be false), and must +check if heap_alloc is true afterwards and free *r_edges. + +r_polys is an array of integer pairs and must be same logical size as r_edges +*/ +void BKE_pbvh_pmap_to_edges(PBVH *pbvh, + PBVHVertRef vertex, + int **r_edges, + int *r_edges_size, + bool *heap_alloc, + int **r_polys); + +void BKE_pbvh_distort_correction_set(PBVH *pbvh, eAttrCorrectMode value); + +void BKE_pbvh_set_face_areas(PBVH *pbvh, float *face_areas); +void BKE_pbvh_set_bmesh(PBVH *pbvh, BMesh *bm); +void BKE_pbvh_free_bmesh(PBVH *pbvh, BMesh *bm); + +void BKE_pbvh_show_orig_set(PBVH *pbvh, bool show_orig); +bool BKE_pbvh_show_orig_get(PBVH *pbvh); + +void BKE_pbvh_flush_tri_areas(Object *ob, PBVH *pbvh); +void BKE_pbvh_bmesh_check_nodes(PBVH *pbvh); + +#include "BLI_math_vector.hh" namespace blender::bke::pbvh { + +void node_update_visibility_mesh(Span hide_vert, PBVHNode &node); +void node_update_visibility_grids(const BitGroupVector<> &grid_hidden, PBVHNode &node); +void node_update_visibility_bmesh(PBVHNode &node); +void update_visibility(PBVH &pbvh); + +void set_flags_valence(PBVH *pbvh, uint8_t *flags, int *valence); +void set_original(PBVH *pbvh, Span origco, Span origno); +void update_vert_boundary_bmesh(int cd_faceset_offset, + int cd_vert_node_offset, + int cd_face_node_offset, + int cd_vcol, + int cd_boundary_flag, + const int cd_flag, + const int cd_valence, + BMVert *v, + const CustomData *ldata, + float sharp_angle_limit); +void update_sharp_vertex_bmesh(BMVert *v, int cd_boundary_flag, const float sharp_angle_limit); + +void update_vert_boundary_faces(int *boundary_flags, + const int *face_sets, + const bool *hide_poly, + const blender::int2 *medge, + const int *corner_verts, + const int *corner_edges, + blender::OffsetIndices polys, + const blender::GroupedSpan &pmap, + PBVHVertRef vertex, + const bool *sharp_edges, + const bool *seam_edges, + uint8_t *flags, + int *valence); +void update_edge_boundary_bmesh(BMEdge *e, + int cd_faceset_offset, + int cd_edge_boundary, + const int cd_flag, + const int cd_valence, + const CustomData *ldata, + float sharp_angle_limit); +void update_edge_boundary_faces(int edge, + Span vertex_positions, + Span vertex_normals, + Span edges, + OffsetIndices polys, + Span poly_normals, + int *edge_boundary_flags, + const int *vert_boundary_flags, + const int *face_sets, + const bool *sharp_edge, + const bool *seam_edge, + const GroupedSpan &pmap, + const GroupedSpan &epmap, + const CustomData *ldata, + float sharp_angle_limit, + blender::Span corner_verts, + blender::Span corner_edges); +void update_edge_boundary_grids(int edge, + Span edges, + OffsetIndices polys, + int *edge_boundary_flags, + const int *vert_boundary_flags, + const int *face_sets, + const bool *sharp_edge, + const bool *seam_edge, + const GroupedSpan &pmap, + const GroupedSpan &epmap, + const CustomData *ldata, + SubdivCCG *subdiv_ccg, + const CCGKey *key, + float sharp_angle_limit, + blender::Span corner_verts, + blender::Span corner_edges); +void update_vert_boundary_grids(PBVH *pbvh, int vertex, const int *face_sets); + +bool check_vert_boundary(PBVH *pbvh, PBVHVertRef vertex, const int *face_sets); +bool check_edge_boundary(PBVH *pbvh, PBVHEdgeRef edge, const int *face_sets); + Vector search_gather(PBVH *pbvh, FunctionRef scb, PBVHNodeFlags leaf_flag = PBVH_Leaf); Vector gather_proxies(PBVH *pbvh); -void node_update_mask_mesh(Span mask, PBVHNode &node); +void node_update_mask_mesh(const Span mask, PBVHNode &node); void node_update_mask_grids(const CCGKey &key, Span grids, PBVHNode &node); void node_update_mask_bmesh(int mask_offset, PBVHNode &node); -void node_update_visibility_mesh(Span hide_vert, PBVHNode &node); -void node_update_visibility_grids(const BitGroupVector<> &grid_hidden, PBVHNode &node); -void node_update_visibility_bmesh(PBVHNode &node); +Vector get_flagged_nodes(PBVH *pbvh, int flag); +void set_pmap(PBVH *pbvh, GroupedSpan pmap); +void set_vemap(PBVH *pbvh, GroupedSpan vemap); +GroupedSpan get_pmap(PBVH *pbvh); -void update_node_bounds_mesh(Span positions, PBVHNode &node); -void update_node_bounds_grids(const CCGKey &key, Span grids, PBVHNode &node); -void update_node_bounds_bmesh(PBVHNode &node); +void sharp_limit_set(PBVH *pbvh, float limit); +float test_sharp_faces_bmesh(BMFace *f1, BMFace *f2, float limit); +float test_sharp_faces_mesh(int f1, + int f2, + float limit, + blender::Span positions, + blender::OffsetIndices &polys, + blender::Span poly_normals, + blender::Span corner_verts); + +blender::Span get_poly_normals(const PBVH *pbvh); +void set_vert_boundary_map(PBVH *pbvh, blender::BitVector<> *vert_boundary_map); +void on_stroke_start(PBVH *pbvh); } // namespace blender::bke::pbvh diff --git a/source/blender/blenkernel/BKE_sculpt.h b/source/blender/blenkernel/BKE_sculpt.h new file mode 100644 index 00000000000..dd7c057109d --- /dev/null +++ b/source/blender/blenkernel/BKE_sculpt.h @@ -0,0 +1,44 @@ +#pragma once + +/** When #BRUSH_ACCUMULATE is used */ +#define SCULPT_TOOL_HAS_ACCUMULATE(t) \ + ELEM(t, \ + SCULPT_TOOL_DRAW, \ + SCULPT_TOOL_DRAW_SHARP, \ + SCULPT_TOOL_SLIDE_RELAX, \ + SCULPT_TOOL_CREASE, \ + SCULPT_TOOL_BLOB, \ + SCULPT_TOOL_INFLATE, \ + SCULPT_TOOL_CLAY, \ + SCULPT_TOOL_CLAY_STRIPS, \ + SCULPT_TOOL_CLAY_THUMB, \ + SCULPT_TOOL_ROTATE, \ + SCULPT_TOOL_SCRAPE, \ + SCULPT_TOOL_FLATTEN) + +#define SCULPT_TOOL_HAS_NORMAL_WEIGHT(t) \ + ELEM(t, SCULPT_TOOL_GRAB, SCULPT_TOOL_SNAKE_HOOK, SCULPT_TOOL_ELASTIC_DEFORM) + +#define SCULPT_TOOL_HAS_RAKE(t) ELEM(t, SCULPT_TOOL_SNAKE_HOOK) + +#define SCULPT_TOOL_HAS_DYNTOPO(t) \ + (ELEM(t, /* These brushes, as currently coded, cannot support dynamic topology */ \ + SCULPT_TOOL_GRAB, \ + SCULPT_TOOL_CLOTH, \ + SCULPT_TOOL_DISPLACEMENT_ERASER, \ + SCULPT_TOOL_ELASTIC_DEFORM, \ + SCULPT_TOOL_BOUNDARY, \ + SCULPT_TOOL_POSE /*SCULPT_TOOL_DRAW_FACE_SETS,*/ \ +\ + /* These brushes could handle dynamic topology, \ \ + * but user feedback indicates it's better not to */ \ + /*SCULPT_TOOL_MASK*/) == 0) + +#define SCULPT_TOOL_HAS_TOPOLOGY_RAKE(t) \ + (ELEM(t, /* These brushes, as currently coded, cannot support topology rake. */ \ + SCULPT_TOOL_GRAB, \ + SCULPT_TOOL_ELASTIC_DEFORM, \ + SCULPT_TOOL_ROTATE, \ + SCULPT_TOOL_DISPLACEMENT_ERASER, \ + SCULPT_TOOL_SLIDE_RELAX, \ + SCULPT_TOOL_MASK) == 0) diff --git a/source/blender/blenkernel/BKE_sculpt.hh b/source/blender/blenkernel/BKE_sculpt.hh new file mode 100644 index 00000000000..0014a54a3cb --- /dev/null +++ b/source/blender/blenkernel/BKE_sculpt.hh @@ -0,0 +1,350 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 by Nicholas Bishop. All rights reserved. */ + +#pragma once + +/** \file + * \ingroup bke + */ + +#include "BKE_attribute.h" +#include "BKE_paint.hh" +#include "BKE_pbvh.hh" + +#include "BLI_compiler_compat.h" +#include "BLI_math_vector.hh" +#include "BLI_math_vector_types.hh" +#include "BLI_span.hh" +#include "BLI_vector.hh" + +#include + +struct Object; + +/* + * Stroke ID API. This API is used to detect if + * an element has already been processed for some task + * inside a given stroke. + */ + +struct StrokeID { + short id; + short userflag; +}; + +enum StrokeIDUser { + STROKEID_USER_AUTOMASKING = 1 << 0, + STROKEID_USER_BOUNDARY = 1 << 1, + STROKEID_USER_SCULPTVERT = 1 << 2, + STROKEID_USER_PREV_COLOR = 1 << 3, + STROKEID_USER_SMOOTH = 1 << 4, + STROKEID_USER_OCCLUSION = 1 << 5, + STROKEID_USER_LAYER_BRUSH = 1 << 6, + STROKEID_USER_ORIGINAL = 1 << 7, +}; +ENUM_OPERATORS(StrokeIDUser, STROKEID_USER_LAYER_BRUSH); + +void BKE_sculpt_reproject_cdata(SculptSession *ss, + PBVHVertRef vertex, + float startco[3], + float startno[3], + eAttrCorrectMode undistort_mode); + +namespace blender::bke::sculpt { +void sculpt_vert_boundary_ensure(Object *ob); + +BLI_INLINE bool stroke_id_clear(SculptSession *ss, PBVHVertRef vertex, StrokeIDUser user) +{ + StrokeID *id = blender::bke::paint::vertex_attr_ptr(vertex, ss->attrs.stroke_id); + + bool retval = id->userflag & user; + id->userflag &= ~user; + + return retval; +} + +BLI_INLINE bool stroke_id_test(SculptSession *ss, PBVHVertRef vertex, StrokeIDUser user) +{ + StrokeID *id = blender::bke::paint::vertex_attr_ptr(vertex, ss->attrs.stroke_id); + bool ret; + + if (id->id != ss->stroke_id) { + id->id = ss->stroke_id; + id->userflag = 0; + ret = true; + } + else { + ret = !(id->userflag & (int)user); + } + + id->userflag |= (int)user; + + return ret; +} + +BLI_INLINE bool stroke_id_test_no_update(SculptSession *ss, PBVHVertRef vertex, StrokeIDUser user) +{ + StrokeID *id = blender::bke::paint::vertex_attr_ptr(vertex, ss->attrs.stroke_id); + + if (id->id != ss->stroke_id) { + return true; + } + + return !(id->userflag & (int)user); +} + +BLI_INLINE void add_sculpt_flag(SculptSession *ss, PBVHVertRef vertex, uint8_t flag) +{ + *blender::bke::paint::vertex_attr_ptr(vertex, ss->attrs.flags) |= flag; +} +BLI_INLINE void clear_sculpt_flag(SculptSession *ss, PBVHVertRef vertex, uint8_t flag) +{ + *blender::bke::paint::vertex_attr_ptr(vertex, ss->attrs.flags) &= ~flag; +} +BLI_INLINE bool test_sculpt_flag(SculptSession *ss, PBVHVertRef vertex, uint8_t flag) +{ + return blender::bke::paint::vertex_attr_get(vertex, ss->attrs.flags) & flag; +} + +/* Interpolates loops surrounding a vertex, splitting any UV map by + * island as appropriate and enforcing proper boundary conditions. + */ +void interp_face_corners(PBVH *pbvh, + PBVHVertRef vertex, + Span loops, + Span ws, + float factor, + int cd_vert_boundary); +float calc_uv_snap_limit(BMLoop *l, int cd_uv); +bool loop_is_corner(BMLoop *l, int cd_uv, float limit = 0.01, const CustomData *ldata = nullptr); + +/* Finds sets of loops with the same vertex data + * prior to an operation, then re-snaps them afterwards. + */ +struct VertLoopSnapper { + Vector, 16> snap_sets; + Span layers; + Span ls; + Vector max_indices; + float limit = 0.001; + + VertLoopSnapper(Span ls_, Span layers_) : layers(layers_), ls(ls_) + { + if (ls.size() == 0) { + return; + } + + snap_sets.resize(ls.size()); + for (auto &snap_set : snap_sets) { + for (int i = 0; i < layers.size(); i++) { + snap_set.append(0); + } + } + + for (int i : layers.index_range()) { + switch (layers[i]->type) { + case CD_PROP_FLOAT: + begin>(i); + break; + case CD_PROP_FLOAT2: + begin(i); + break; + case CD_PROP_FLOAT3: + begin(i); + break; + case CD_PROP_COLOR: + begin(i); + break; + } + } + } + + void snap() + { + for (int i : layers.index_range()) { + switch (layers[i]->type) { + case CD_PROP_FLOAT: + do_snap>(i); + break; + case CD_PROP_FLOAT2: + do_snap(i); + break; + case CD_PROP_FLOAT3: + do_snap(i); + break; + case CD_PROP_COLOR: + do_snap(i); + break; + } + } + } + + private: + template void begin(int layer_i) + { + CustomDataLayer *layer = layers[layer_i]; + int idx_base = 1; + + limit = 0.001f; + + if constexpr (std::is_same_v) { + /* Set UV snap limit as 1/10th the average uv edge length. */ + limit = 0.1f; + float len = 0.0f; + + for (BMLoop *l : ls) { + T value1 = *BM_ELEM_CD_PTR(l, layer->offset); + T value2 = *BM_ELEM_CD_PTR(l->next, layer->offset); + + len += fabsf(value1[0] - value2[0]) * 0.5f; + len += fabsf(value1[1] - value2[1]) * 0.5f; + } + + len /= ls.size(); + limit *= len; + } + + for (int i : ls.index_range()) { + if (snap_sets[i][layer_i] != 0) { + continue; + } + + T a = *BM_ELEM_CD_PTR(ls[i], layer->offset); + int set = snap_sets[i][layer_i] = idx_base++; + + for (int j : ls.index_range()) { + if (snap_sets[j][layer_i] != 0) { + continue; + } + + T b = *BM_ELEM_CD_PTR(ls[j], layer->offset); + if (math::distance_squared(a, b) <= limit * limit) { + snap_sets[j][layer_i] = set; + } + } + } + + max_indices.append(idx_base); + } + + template void do_snap(int layer_i) + { + const int cd_offset = layers[layer_i]->offset; + + for (int set_i : IndexRange(max_indices[layer_i])) { + T sum = {}; + float tot = 0.0f; + + for (int i : ls.index_range()) { + if (snap_sets[i][layer_i] == set_i) { + sum += *BM_ELEM_CD_PTR(ls[i], cd_offset); + tot += 1.0f; + } + } + + if (tot == 0.0f) { + continue; + } + + sum /= tot; + + for (int i : ls.index_range()) { + if (snap_sets[i][layer_i] == set_i) { + *BM_ELEM_CD_PTR(ls[i], cd_offset) = sum; + } + } + } + } +}; +} // namespace blender::bke::sculpt + +/* Uncomment to enable PBVH NaN debugging. */ +//#define PBVH_CHECK_NANS + +#ifdef PBVH_CHECK_NANS +# include "atomic_ops.h" +# include +# include + +/* Why is atomic_ops defining near & far macros? */ +# ifdef near +# undef near +# endif +# ifdef far +# undef far +# endif + +// static global to limit the number of reports per source file +static int _bke_pbvh_report_count = 0; + +# define PBVH_NAN_REPORT_LIMIT 16 + +// for debugging NaNs that don't appear on developer's machines +static ATTR_NO_OPT bool _pbvh_nan_check1(const float f, + const char *func, + const char *file, + int line) +{ + bool bad = false; + + if (_bke_pbvh_report_count > PBVH_NAN_REPORT_LIMIT) { + return false; + } + + if (isnan(f) || !isfinite(f)) { + const char *type = !isfinite(f) ? "infinity" : "nan"; + printf("float corruption (value was %s): %s:%d\n\t%s\n", type, func, line, file); + bad = true; + } + + if (bad) { + atomic_add_and_fetch_int32(&_bke_pbvh_report_count, 1); + } + + return bad; +} + +static ATTR_NO_OPT bool _pbvh_nan_check3(const float co[3], + const char *func, + const char *file, + int line) +{ + if (!co) { + return false; + } + + bool ret = false; + + for (int i = 0; i < 3; i++) { + ret |= _pbvh_nan_check1(co[i], func, file, line); + } + + return ret; +} + +static ATTR_NO_OPT bool _pbvh_nan_check4(const float co[4], + const char *func, + const char *file, + int line) +{ + if (!co) { + return false; + } + + bool ret = false; + + for (int i = 0; i < 4; i++) { + ret |= _pbvh_nan_check1(co[i], func, file, line); + } + + return ret; +} + +# define PBVH_CHECK_NAN(co) _pbvh_nan_check3(co, __func__, __FILE__, __LINE__) +# define PBVH_CHECK_NAN1(f) _pbvh_nan_check1(f, __func__, __FILE__, __LINE__) +# define PBVH_CHECK_NAN4(co) _pbvh_nan_check4(co, __func__, __FILE__, __LINE__) +#else +# define PBVH_CHECK_NAN(co) +# define PBVH_CHECK_NAN1(f) +# define PBVH_CHECK_NAN4(co) +#endif diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 4a5827e644a..5cec3e8afb0 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -39,6 +39,8 @@ set(INC_SYS # For `vfontdata_freetype.cc`. ${FREETYPE_INCLUDE_DIRS} + + ${EIGEN3_INCLUDE_DIRS} ) set(SRC @@ -121,6 +123,8 @@ set(SRC intern/data_transfer.cc intern/deform.cc intern/displist.cc + intern/dyntopo.cc + intern/dyntopo_collapse.cc intern/dynamicpaint.cc intern/editlattice.cc intern/editmesh.cc @@ -387,6 +391,8 @@ set(SRC BKE_displist.h BKE_duplilist.hh BKE_dynamicpaint.h + BKE_dyntopo.hh + BKE_dyntopo_set.hh BKE_editlattice.h BKE_editmesh.hh BKE_editmesh_bvh.h @@ -489,6 +495,8 @@ set(SRC BKE_preview_image.hh BKE_report.hh BKE_rigidbody.h + BKE_sculpt.h + BKE_sculpt.hh BKE_scene.hh BKE_scene_runtime.hh BKE_screen.hh @@ -537,6 +545,7 @@ set(SRC intern/CCGSubSurf.h intern/CCGSubSurf_inline.h intern/CCGSubSurf_intern.h + intern/dyntopo_intern.hh intern/attribute_access_intern.hh intern/data_transfer_intern.h intern/lib_intern.hh @@ -864,3 +873,4 @@ if(WITH_GTESTS) ) blender_add_test_suite_lib(blenkernel "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${TEST_LIB}") endif() + diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index 6b947417e91..abb8bdc78fd 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -8,6 +8,9 @@ #include "BKE_customdata.hh" #include "BKE_deform.hh" #include "BKE_geometry_set.hh" +#include "BKE_global.hh" +#include "BKE_mesh.hh" +#include "BKE_pointcloud.hh" #include "BKE_type_conversions.hh" #include "DNA_meshdata_types.h" @@ -116,6 +119,10 @@ const char *no_procedural_access_message = N_( bool allow_procedural_attribute_access(StringRef attribute_name) { + if (G.debug_value == 892) { + return true; + } + if (attribute_name.startswith(".corner")) { return false; } diff --git a/source/blender/blenkernel/intern/brush.cc b/source/blender/blenkernel/intern/brush.cc index 7d81cd747f9..b6e2b68d7d7 100644 --- a/source/blender/blenkernel/intern/brush.cc +++ b/source/blender/blenkernel/intern/brush.cc @@ -1783,6 +1783,8 @@ void BKE_brush_sculpt_reset(Brush *br) brush_defaults(br); BKE_brush_curve_preset(br, CURVE_PRESET_SMOOTH); + br->dyntopo = *DNA_struct_default_get(DynTopoSettings); + /* Use the curve presets by default */ br->curve_preset = BRUSH_CURVE_SMOOTH; @@ -1790,6 +1792,8 @@ void BKE_brush_sculpt_reset(Brush *br) * assign this so logic below can remain the same. */ br->alpha = 0.5f; + bool disable_dyntopo = false; + /* Brush settings */ switch (br->sculpt_tool) { case SCULPT_TOOL_DRAW_SHARP: @@ -1801,11 +1805,13 @@ void BKE_brush_sculpt_reset(Brush *br) br->curve_preset = BRUSH_CURVE_SMOOTHER; br->spacing = 10; br->alpha = 1.0f; + disable_dyntopo = true; break; case SCULPT_TOOL_SLIDE_RELAX: br->spacing = 10; br->alpha = 1.0f; br->slide_deform_type = BRUSH_SLIDE_DEFORM_DRAG; + disable_dyntopo = true; break; case SCULPT_TOOL_CLAY: br->flag |= BRUSH_SIZE_PRESSURE; @@ -1853,6 +1859,7 @@ void BKE_brush_sculpt_reset(Brush *br) break; case SCULPT_TOOL_ROTATE: br->alpha = 1.0; + disable_dyntopo = true; break; case SCULPT_TOOL_SMOOTH: br->flag &= ~BRUSH_SPACE_ATTEN; @@ -1861,10 +1868,17 @@ void BKE_brush_sculpt_reset(Brush *br) br->surface_smooth_shape_preservation = 0.5f; br->surface_smooth_current_vertex = 0.5f; br->surface_smooth_iterations = 4; + disable_dyntopo = true; break; case SCULPT_TOOL_SNAKE_HOOK: br->alpha = 1.0f; br->rake_factor = 1.0f; + br->dyntopo.flag |= DYNTOPO_SUBDIVIDE | DYNTOPO_COLLAPSE; + br->dyntopo.flag &= ~DYNTOPO_CLEANUP; + br->dyntopo.spacing = 20; + br->dyntopo.quality = 0.35f; + br->dyntopo.radius_scale = 1.0; + br->dyntopo.repeat = 0; break; case SCULPT_TOOL_THUMB: br->size = 75; @@ -1898,6 +1912,7 @@ void BKE_brush_sculpt_reset(Brush *br) br->flag &= ~BRUSH_ALPHA_PRESSURE; br->flag &= ~BRUSH_SPACE; br->flag &= ~BRUSH_SPACE_ATTEN; + disable_dyntopo = true; break; case SCULPT_TOOL_GRAB: br->alpha = 0.4f; @@ -1905,6 +1920,7 @@ void BKE_brush_sculpt_reset(Brush *br) br->flag &= ~BRUSH_ALPHA_PRESSURE; br->flag &= ~BRUSH_SPACE; br->flag &= ~BRUSH_SPACE_ATTEN; + disable_dyntopo = true; break; case SCULPT_TOOL_CLOTH: br->cloth_mass = 1.0f; @@ -1913,6 +1929,7 @@ void BKE_brush_sculpt_reset(Brush *br) br->cloth_sim_falloff = 0.75f; br->cloth_deform_type = BRUSH_CLOTH_DEFORM_DRAG; br->flag &= ~(BRUSH_ALPHA_PRESSURE | BRUSH_SIZE_PRESSURE); + disable_dyntopo = true; break; case SCULPT_TOOL_LAYER: br->flag &= ~BRUSH_SPACE_ATTEN; @@ -1927,6 +1944,7 @@ void BKE_brush_sculpt_reset(Brush *br) br->flow = 1.0f; br->density = 1.0f; br->flag &= ~BRUSH_SPACE_ATTEN; + disable_dyntopo = false; zero_v3(br->rgb); copy_v3_fl(br->secondary_rgb, 1.0f); break; @@ -1936,6 +1954,7 @@ void BKE_brush_sculpt_reset(Brush *br) br->flag &= ~BRUSH_ALPHA_PRESSURE; br->flag &= ~BRUSH_SPACE_ATTEN; br->curve_preset = BRUSH_CURVE_SPHERE; + disable_dyntopo = true; break; case SCULPT_TOOL_DISPLACEMENT_SMEAR: br->alpha = 1.0f; @@ -1945,10 +1964,18 @@ void BKE_brush_sculpt_reset(Brush *br) br->flag &= ~BRUSH_SPACE_ATTEN; br->curve_preset = BRUSH_CURVE_SMOOTHER; break; + case SCULPT_TOOL_SIMPLIFY: + br->dyntopo.flag |= DYNTOPO_COLLAPSE | DYNTOPO_SUBDIVIDE | DYNTOPO_CLEANUP; + break; + case SCULPT_TOOL_MASK: + disable_dyntopo = true; + break; default: break; } + br->dyntopo.inherit = BKE_brush_dyntopo_inherit_flags(br); + /* Cursor colors */ /* Default Alpha */ @@ -1986,16 +2013,17 @@ void BKE_brush_sculpt_reset(Brush *br) br->sub_col[2] = 0.117f; break; - case SCULPT_TOOL_PINCH: case SCULPT_TOOL_GRAB: - case SCULPT_TOOL_SNAKE_HOOK: - case SCULPT_TOOL_THUMB: - case SCULPT_TOOL_NUDGE: case SCULPT_TOOL_ROTATE: case SCULPT_TOOL_ELASTIC_DEFORM: case SCULPT_TOOL_POSE: case SCULPT_TOOL_BOUNDARY: case SCULPT_TOOL_SLIDE_RELAX: + disable_dyntopo = true; + case SCULPT_TOOL_THUMB: + case SCULPT_TOOL_NUDGE: + case SCULPT_TOOL_PINCH: + case SCULPT_TOOL_SNAKE_HOOK: br->add_col[0] = 1.0f; br->add_col[1] = 0.95f; br->add_col[2] = 0.005f; @@ -2029,6 +2057,10 @@ void BKE_brush_sculpt_reset(Brush *br) default: break; } + + if (disable_dyntopo) { + br->dyntopo.flag |= DYNTOPO_DISABLED; + } } void BKE_brush_curve_preset(Brush *b, eCurveMappingPreset preset) @@ -2654,6 +2686,32 @@ ImBuf *BKE_brush_gen_radial_control_imbuf(Brush *br, bool secondary, bool displa return im; } +bool BKE_brush_hard_edge_mode_get(const Scene *scene, const Brush *brush) +{ + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + bool ret = (ups->flag & UNIFIED_PAINT_FLAG_HARD_EDGE_MODE) ? ups->hard_edge_mode : + brush->flag2 & BRUSH_HARD_EDGE_MODE; + + return ret; +} + +void BKE_brush_hard_edge_mode_set(Scene *scene, Brush *brush, bool val) +{ + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + + if (ups->flag & UNIFIED_PAINT_FLAG_HARD_EDGE_MODE) { + ups->hard_edge_mode = val; + } + else { + if (val) { + brush->flag2 |= BRUSH_HARD_EDGE_MODE; + } + else { + brush->flag2 &= ~BRUSH_HARD_EDGE_MODE; + } + } +} + bool BKE_brush_has_cube_tip(const Brush *brush, PaintMode paint_mode) { switch (paint_mode) { @@ -2677,3 +2735,32 @@ bool BKE_brush_has_cube_tip(const Brush *brush, PaintMode paint_mode) return false; } + +float BKE_brush_hard_corner_pin_get(const Scene *scene, const Brush *brush) +{ + if (scene && scene->toolsettings->unified_paint_settings.flag & UNIFIED_PAINT_HARD_CORNER_PIN) { + return scene->toolsettings->unified_paint_settings.hard_corner_pin; + } + + return brush->hard_corner_pin; +} + +int BKE_brush_dyntopo_inherit_flags(Brush *brush) +{ + if (!brush) { + return DYNTOPO_INHERIT_BITMASK; + } + + if (brush->sculpt_tool == SCULPT_TOOL_SIMPLIFY) { + return ~(DYNTOPO_INHERIT_COLLAPSE | DYNTOPO_INHERIT_SUBDIVIDE | DYNTOPO_INHERIT_CLEANUP | + DYNTOPO_INHERIT_DISABLED); + } + if (brush->sculpt_tool == SCULPT_TOOL_SNAKE_HOOK) { + return ~(DYNTOPO_INHERIT_SPACING | DYNTOPO_INHERIT_SUBDIVIDE | DYNTOPO_INHERIT_COLLAPSE | + DYNTOPO_INHERIT_RADIUS_SCALE | DYNTOPO_INHERIT_REPEAT | DYNTOPO_INHERIT_CLEANUP | + DYNTOPO_INHERIT_DISABLED | DYNTOPO_INHERIT_QUALITY); + } + + /* Inherit everything from scene defaults */ + return DYNTOPO_INHERIT_BITMASK & ~DYNTOPO_INHERIT_DISABLED; +} diff --git a/source/blender/blenkernel/intern/customdata.cc b/source/blender/blenkernel/intern/customdata.cc index 586819971e5..1b1abe7257e 100644 --- a/source/blender/blenkernel/intern/customdata.cc +++ b/source/blender/blenkernel/intern/customdata.cc @@ -18,9 +18,12 @@ #include "DNA_customdata_types.h" #include "DNA_meshdata_types.h" +#include "BLI_array.hh" +#include "BLI_asan.h" #include "BLI_bit_vector.hh" #include "BLI_bitmap.h" #include "BLI_color.hh" +#include "BLI_compiler_attrs.h" #include "BLI_endian_switch.h" #include "BLI_index_range.hh" #include "BLI_math_color_blend.h" @@ -36,6 +39,7 @@ #include "BLI_string_utf8.h" #include "BLI_string_utils.hh" #include "BLI_utildefines.h" +#include "BLI_vector.hh" #ifndef NDEBUG # include "BLI_dynstr.h" @@ -59,9 +63,15 @@ #include "CLG_log.h" +#ifdef WITH_ASAN +# define BM_ASAN_PAD 32 +#endif + /* only for customdata_data_transfer_interp_normal_normals */ #include "data_transfer_intern.h" +#include + using blender::Array; using blender::BitVector; using blender::float2; @@ -82,6 +92,39 @@ BLI_STATIC_ASSERT(BOUNDED_ARRAY_TYPE_SIZE() == CD static CLG_LogRef LOG = {"bke.customdata"}; +bool CustomData_layout_is_same(const CustomData *_a, const CustomData *_b) +{ + CustomData a = *_a; + CustomData b = *_b; + + if (a.totlayer != b.totlayer) { + return false; + } + + a.layers = b.layers = nullptr; + a.pool = b.pool = nullptr; + a.maxlayer = b.maxlayer; + + if (memcmp((void *)&a, (void *)&b, sizeof(CustomData)) != 0) { + return false; + } + + for (int i = 0; i < a.totlayer; i++) { + CustomDataLayer cla = _a->layers[i]; + CustomDataLayer clb = _b->layers[i]; + + cla.data = clb.data = nullptr; + cla.anonymous_id = clb.anonymous_id = nullptr; + cla.sharing_info = clb.sharing_info = nullptr; + + if (memcmp((void *)&cla, (void *)&clb, sizeof(CustomDataLayer)) != 0) { + return false; + } + } + + return true; +} + /* -------------------------------------------------------------------- */ /** \name Mesh Mask Utilities * \{ */ @@ -2216,6 +2259,7 @@ const CustomData_MeshMasks CD_MASK_MESH = { /*lmask*/ (CD_MASK_MDISPS | CD_MASK_CUSTOMLOOPNORMAL | CD_MASK_GRID_PAINT_MASK | CD_MASK_PROP_ALL), }; + const CustomData_MeshMasks CD_MASK_DERIVEDMESH = { /*vmask*/ (CD_MASK_ORIGINDEX | CD_MASK_MDEFORMVERT | CD_MASK_SHAPEKEY | CD_MASK_MVERT_SKIN | CD_MASK_ORCO | CD_MASK_CLOTH_ORCO | CD_MASK_PROP_ALL), @@ -2344,7 +2388,91 @@ void CustomData_update_typemap(CustomData *data) } } -/* currently only used in BLI_assert */ +void CustomData_regen_active_refs(CustomData *data) +{ + int i, j; + + for (int i = 0; i < CD_NUMTYPES; i++) { + data->typemap[i] = -1; + } + + for (i = 0, j = 0; i < data->totlayer; i++) { + CustomDataLayer *layer = &data->layers[i]; + + if (data->typemap[layer->type] == -1) { + data->typemap[layer->type] = i; + } + } + + /* Explicitly flag active layers. */ + for (i = 0, j = 0; i < data->totlayer; i++) { + CustomDataLayer *layer = &data->layers[i]; + CustomDataLayer *base = data->layers + data->typemap[layer->type]; + int n = layer - base; + + if (layer == base) { + continue; + } + + layer->active = n == base->active; + layer->active_clone = n == base->active_clone; + layer->active_mask = n == base->active_mask; + layer->active_rnd = n == base->active_rnd; + } + + /* Handle case of base layers being active. */ + for (int i = 0; i < CD_NUMTYPES; i++) { + if (data->typemap[i] == -1) { + continue; + } + + CustomDataLayer *base = data->layers + data->typemap[i]; + + base->active = !base->active; + base->active_mask = !base->active_mask; + base->active_clone = !base->active_clone; + base->active_rnd = !base->active_rnd; + } + + /* Regenerate active refs, + * set active n in base layer for all types. + */ + for (i = 0; i < data->totlayer; i++) { + CustomDataLayer *layer = data->layers + i; + CustomDataLayer *base = data->layers + data->typemap[layer->type]; + + int n = layer - base; + + if (n < 0) { + BLI_assert_unreachable(); + } + if (layer->active) { + base->active = n; + } + if (layer->active_mask) { + base->active_mask = n; + } + if (layer->active_clone) { + base->active_clone = n; + } + if (layer->active_rnd) { + base->active_rnd = n; + } + } + + /* set active n in all layers */ + for (i = 0; i < data->totlayer; i++) { + CustomDataLayer *layer = &data->layers[i]; + CustomDataLayer *base = data->layers + data->typemap[layer->type]; + + layer->active = base->active; + layer->active_mask = base->active_mask; + layer->active_clone = base->active_clone; + layer->active_rnd = base->active_rnd; + } +} + +/* Currently only used in BLI_assert. */ #ifndef NDEBUG static bool customdata_typemap_is_valid(const CustomData *data) { @@ -2354,6 +2482,33 @@ static bool customdata_typemap_is_valid(const CustomData *data) } #endif +/* Copies all customdata layers without allocating data, + * and without respect to type masks or NO_COPY/etc flags. + */ +void CustomData_copy_all_layout(const struct CustomData *source, struct CustomData *dest) +{ + *dest = *source; + dest->external = nullptr; + dest->pool = nullptr; + + if (source->layers) { + dest->layers = static_cast( + MEM_mallocN(sizeof(*dest->layers) * source->maxlayer, __func__)); + + for (int i = 0; i < source->totlayer; i++) { + CustomDataLayer *layer = &dest->layers[i]; + + *layer = source->layers[i]; + layer->data = nullptr; + layer->sharing_info = nullptr; + } + } + + CustomData_update_typemap(dest); + CustomData_regen_active_refs(dest); + customData_update_offsets(dest); +} + static void *copy_layer_data(const eCustomDataType type, const void *data, const int totelem) { const LayerTypeInfo &type_info = *layerType_getInfo(type); @@ -2471,6 +2626,8 @@ static bool customdata_merge_internal(const CustomData *source, } CustomData_update_typemap(dest); + customData_update_offsets(dest); + return changed; } @@ -2753,22 +2910,121 @@ void CustomData_free_typemask(CustomData *data, const int totelem, eCustomDataMa CustomData_reset(data); } +static int customData_get_alignment(eCustomDataType type) +{ + /* Handle array types. */ + if (ELEM(type, + CD_PROP_FLOAT2, + CD_PROP_FLOAT3, + CD_PROP_QUATERNION, + CD_PROP_COLOR, + CD_NORMAL, + CD_TANGENT, + CD_SHAPEKEY, + CD_ORIGSPACE_MLOOP, + CD_PROP_INT32_2D)) + { + return 4; + } + + if (ELEM(type, CD_TESSLOOPNORMAL)) { + return 2; + } + + if (type == CD_PROP_BYTE_COLOR) { + return 1; + } + + /* Derive the alignment from the element size. */ + int size = CustomData_sizeof(type); + + if (size >= 8) { + return 8; + } + if (size >= 4) { + return 4; + } + if (size >= 2) { + return 2; + } + + return 1; +} + +/* Update BMesh block offsets, respects alignment. */ static void customData_update_offsets(CustomData *data) { - const LayerTypeInfo *typeInfo; + if (data->totlayer == 0) { + data->totsize = 0; + CustomData_update_typemap(data); + return; + } + + const std::array aligns = {8, 4, 2, 1}; + int max_alignment = 1; + int offset = 0; +#ifdef WITH_ASAN + offset += BM_ASAN_PAD; +#endif - for (int i = 0; i < data->totlayer; i++) { - typeInfo = layerType_getInfo(eCustomDataType(data->layers[i].type)); + for (const int align : aligns) { + for (const int i : IndexRange(data->totlayer)) { + CustomDataLayer *layer = data->layers + i; - data->layers[i].offset = offset; - offset += typeInfo->size; + const int layer_align = customData_get_alignment(eCustomDataType(layer->type)); + if (layer_align != align) { + continue; + } + + layer->offset = offset; + + int size = CustomData_sizeof(eCustomDataType(layer->type)); + if (size % align != 0) { + size += align - (size % align); + } + + offset += size; +#ifdef WITH_ASAN + offset += BM_ASAN_PAD; +#endif + + max_alignment = max_ii(max_alignment, align); + } + } + + if (offset % max_alignment != 0) { + offset += max_alignment - (offset % max_alignment); } data->totsize = offset; CustomData_update_typemap(data); } +#ifdef WITH_ASAN +void CustomData_bmesh_poison(const CustomData *data, void *block) +{ + BLI_asan_poison(block, data->totsize); + for (int i = 0; i < data->totlayer; i++) { + CustomDataLayer *layer = data->layers + i; + size_t size = CustomData_sizeof(eCustomDataType(layer->type)); + + BLI_asan_unpoison(POINTER_OFFSET(block, layer->offset), size); + } +} +#else +void CustomData_bmesh_poison(const CustomData * /*data*/, void * /*block*/) {} +#endif + +#ifdef WITH_ASAN +void CustomData_bmesh_unpoison(const CustomData *data, void *block) +{ + BLI_asan_unpoison(block, data->totsize); +} +#else +void CustomData_bmesh_unpoison(const CustomData *, void *) {} +#endif + /* to use when we're in the middle of modifying layers */ static int CustomData_get_layer_index__notypemap(const CustomData *data, const eCustomDataType type) @@ -3419,7 +3675,7 @@ int CustomData_number_of_anonymous_layers(const CustomData *data, const eCustomD return number; } -int CustomData_number_of_layers_typemask(const CustomData *data, const eCustomDataMask mask) +int CustomData_number_of_layers_typemask(const CustomData *data, eCustomDataMask mask) { int number = 0; @@ -3652,6 +3908,24 @@ void CustomData_interp(const CustomData *source, sources[j] = POINTER_OFFSET(src_data, size_t(src_indices[j]) * typeInfo->size); } + if (dest->layers[dest_i].flag & CD_FLAG_ELEM_NOINTERP) { + if (!(dest->layers[dest_i].flag & CD_FLAG_ELEM_NOCOPY)) { + if (typeInfo->copy) { + typeInfo->copy( + sources[0], + POINTER_OFFSET(dest->layers[dest_i].data, (size_t)dest_index * typeInfo->size), + 1); + } + else { + memcpy(POINTER_OFFSET(dest->layers[dest_i].data, (size_t)dest_index * typeInfo->size), + sources[0], + typeInfo->size); + } + } + + continue; + } + typeInfo->interp( sources, weights, @@ -3983,6 +4257,7 @@ void CustomData_bmesh_free_block(CustomData *data, void **block) } if (data->totsize) { + CustomData_bmesh_unpoison(data, *block); BLI_mempool_free(data->pool, *block); } @@ -3994,15 +4269,19 @@ void CustomData_bmesh_free_block_data(CustomData *data, void *block) if (block == nullptr) { return; } + for (int i = 0; i < data->totlayer; i++) { const LayerTypeInfo *typeInfo = layerType_getInfo(eCustomDataType(data->layers[i].type)); + if (typeInfo->free) { const size_t offset = data->layers[i].offset; typeInfo->free(POINTER_OFFSET(block, offset), 1); } - } - if (data->totsize) { - memset(block, 0, data->totsize); + + /* Do not clear data for elem nocopy layers, e.g. element IDs. */ + if (!(data->layers[i].flag & CD_FLAG_ELEM_NOCOPY)) { + memset(POINTER_OFFSET(block, data->layers[i].offset), 0, typeInfo->size); + } } } @@ -4014,6 +4293,8 @@ void CustomData_bmesh_alloc_block(CustomData *data, void **block) if (data->totsize > 0) { *block = BLI_mempool_alloc(data->pool); + + CustomData_bmesh_poison(data, *block); } else { *block = nullptr; @@ -4027,6 +4308,7 @@ void CustomData_bmesh_free_block_data_exclude_by_type(CustomData *data, if (block == nullptr) { return; } + for (int i = 0; i < data->totlayer; i++) { if ((CD_TYPE_AS_MASK(data->layers[i].type) & mask_exclude) == 0) { const LayerTypeInfo *typeInfo = layerType_getInfo(eCustomDataType(data->layers[i].type)); @@ -4039,7 +4321,7 @@ void CustomData_bmesh_free_block_data_exclude_by_type(CustomData *data, } } -void CustomData_data_set_default_value(const eCustomDataType type, void *elem) +void CustomData_data_set_default_value(eCustomDataType type, void *elem) { const LayerTypeInfo *typeInfo = layerType_getInfo(type); if (typeInfo->set_default_value) { @@ -4068,6 +4350,76 @@ void CustomData_bmesh_set_default(CustomData *data, void **block) } } +void CustomData_bmesh_swap_data(CustomData *source, + CustomData *dest, + void *src_block, + void **dest_block) +{ + int src_i = 0; + int dest_i = 0; + int dest_i_start = 0; + + if (*dest_block == nullptr) { + CustomData_bmesh_alloc_block(dest, dest_block); + + if (*dest_block) { + CustomData_bmesh_unpoison(dest, *dest_block); + memset(*dest_block, 0, dest->totsize); + CustomData_bmesh_poison(dest, *dest_block); + + CustomData_bmesh_set_default(dest, dest_block); + } + } + + for (src_i = 0; src_i < source->totlayer; src_i++) { + /* Find the first dest layer with type >= the source type + * (this should work because layers are ordered by type). + */ + while (dest_i_start < dest->totlayer && + dest->layers[dest_i_start].type < source->layers[src_i].type) + { + dest_i_start++; + } + + /* If there are no more dest layers, we're done. */ + if (dest_i_start >= dest->totlayer) { + return; + } + + dest_i = dest_i_start; + + while (dest_i < dest->totlayer && dest->layers[dest_i].type == source->layers[src_i].type) { + /* If we found a matching layer, copy the data. */ + if (dest->layers[dest_i].type == source->layers[src_i].type && + STREQ(dest->layers[dest_i].name, source->layers[src_i].name)) + { + void *src_data = POINTER_OFFSET(src_block, source->layers[src_i].offset); + void *dest_data = POINTER_OFFSET(*dest_block, dest->layers[dest_i].offset); + const LayerTypeInfo *typeInfo = layerType_getInfo( + eCustomDataType(source->layers[src_i].type)); + const uint size = typeInfo->size; + + /* Swap data. */ + char *bsrc = (char *)src_data; + char *bdst = (char *)dest_data; + + for (int j = 0; j < size; j++) { + char t = *bsrc; + *bsrc = *bdst; + *bdst = t; + + bsrc++; + bdst++; + } + + break; + } + + dest_i++; + } + } +} + BMCustomDataCopyMap CustomData_bmesh_copy_map_calc(const CustomData &src, const CustomData &dst, const eCustomDataMask mask_exclude) @@ -4145,7 +4497,7 @@ void CustomData_bmesh_copy_block(CustomData &data, void *src_block, void **dst_b if (*dst_block) { for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) { const LayerTypeInfo &info = *layerType_getInfo(eCustomDataType(layer.type)); - if (info.free) { + if (info.free && !(layer.flag & CD_FLAG_ELEM_NOCOPY)) { info.free(POINTER_OFFSET(*dst_block, layer.offset), 1); } } @@ -4154,10 +4506,14 @@ void CustomData_bmesh_copy_block(CustomData &data, void *src_block, void **dst_b if (data.totsize == 0) { return; } - *dst_block = BLI_mempool_alloc(data.pool); + *dst_block = BLI_mempool_calloc(data.pool); } for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) { + if (layer.flag & CD_FLAG_ELEM_NOCOPY) { + continue; + } + const int offset = layer.offset; const LayerTypeInfo &info = *layerType_getInfo(eCustomDataType(layer.type)); if (info.copy) { @@ -4169,6 +4525,78 @@ void CustomData_bmesh_copy_block(CustomData &data, void *src_block, void **dst_b } } +static bool customdata_layer_copy_check(const CustomDataLayer &source, const CustomDataLayer &dest) +{ + return source.type == dest.type && STREQ(source.name, dest.name) && + !(source.flag & CD_FLAG_ELEM_NOCOPY); +} + +void CustomData_bmesh_copy_data_exclude_by_type(const CustomData *source, + CustomData *dest, + void *src_block, + void **dest_block, + const eCustomDataMask mask_exclude) +{ + bool was_new = false; + + if (*dest_block == nullptr) { + CustomData_bmesh_alloc_block(dest, dest_block); + + if (*dest_block) { + CustomData_bmesh_unpoison(dest, *dest_block); + memset(*dest_block, 0, dest->totsize); + CustomData_bmesh_poison(dest, *dest_block); + + was_new = true; + } + } + + BitVector<> copied_layers(dest->totlayer); + + for (int layer_src_i : IndexRange(source->totlayer)) { + const CustomDataLayer &layer_src = source->layers[layer_src_i]; + + if (CD_TYPE_AS_MASK(layer_src.type) & mask_exclude) { + continue; + } + + for (int layer_dst_i : IndexRange(dest->totlayer)) { + CustomDataLayer &layer_dst = dest->layers[layer_dst_i]; + + if (!customdata_layer_copy_check(layer_src, layer_dst)) { + continue; + } + + copied_layers[layer_dst_i].set(true); + + const void *src_data = POINTER_OFFSET(src_block, layer_src.offset); + void *dest_data = POINTER_OFFSET(*dest_block, layer_dst.offset); + const LayerTypeInfo *typeInfo = layerType_getInfo(eCustomDataType(layer_src.type)); + if (typeInfo->copy) { + typeInfo->copy(src_data, dest_data, 1); + } + else { + memcpy(dest_data, src_data, typeInfo->size); + } + } + } + + /* Initialize dest layers that weren't in source. */ + for (int layer_dst_i : IndexRange(dest->totlayer)) { + if (was_new && !copied_layers[layer_dst_i]) { + CustomData_bmesh_set_default_n(dest, dest_block, layer_dst_i); + } + } +} + +void CustomData_bmesh_copy_data(const CustomData *source, + CustomData *dest, + void *src_block, + void **dest_block) +{ + CustomData_bmesh_copy_data_exclude_by_type(source, dest, src_block, dest_block, 0); +} + void *CustomData_bmesh_get(const CustomData *data, void *block, const eCustomDataType type) { int layer_index = CustomData_get_active_layer_index(data, type); @@ -4379,12 +4807,13 @@ void CustomData_bmesh_interp_n(CustomData *data, typeInfo->interp(src_blocks_ofs, weights, sub_weights, count, dst_block_ofs); } -void CustomData_bmesh_interp(CustomData *data, - const void **src_blocks, - const float *weights, - const float *sub_weights, - int count, - void *dst_block) +void CustomData_bmesh_interp_ex(CustomData *data, + const void **src_blocks, + const float *weights, + const float *sub_weights, + int count, + void *dst_block, + eCustomDataMask typemask) { if (count <= 0) { return; @@ -4412,7 +4841,32 @@ void CustomData_bmesh_interp(CustomData *data, /* interpolates a layer at a time */ for (int i = 0; i < data->totlayer; i++) { CustomDataLayer *layer = &data->layers[i]; + const LayerTypeInfo *typeInfo = layerType_getInfo(eCustomDataType(layer->type)); + + if (!(CD_TYPE_AS_MASK(layer->type) & typemask)) { + continue; + } + + if (layer->flag & CD_FLAG_ELEM_NOINTERP) { + if (layer->flag & CD_FLAG_ELEM_NOCOPY) { + continue; + } + + if (typeInfo->copy) { + typeInfo->copy(POINTER_OFFSET(src_blocks[0], layer->offset), + POINTER_OFFSET(dst_block, layer->offset), + 1); + } + else { + memcpy(POINTER_OFFSET(dst_block, layer->offset), + POINTER_OFFSET(src_blocks[0], layer->offset), + typeInfo->size); + } + + continue; + } + if (typeInfo->interp) { for (int j = 0; j < count; j++) { sources[j] = POINTER_OFFSET(src_blocks[j], layer->offset); @@ -4429,6 +4883,17 @@ void CustomData_bmesh_interp(CustomData *data, MEM_freeN(default_weights); } } +void CustomData_bmesh_interp(CustomData *data, + const void **src_blocks, + const float *weights, + const float *sub_weights, + int count, + void *dst_block) + +{ + eCustomDataMask typemask = eCustomDataMask((1ULL << CD_NUMTYPES) - 1ULL); + CustomData_bmesh_interp_ex(data, src_blocks, weights, sub_weights, count, dst_block, typemask); +} void CustomData_file_write_info(const eCustomDataType type, const char **r_struct_name, @@ -4445,7 +4910,7 @@ void CustomData_blend_write_prepare(CustomData &data, const Set &skip_names) { for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) { - if (layer.flag & CD_FLAG_NOCOPY) { + if (layer.flag & (CD_FLAG_NOCOPY | CD_FLAG_TEMPORARY)) { continue; } if (layer.anonymous_id != nullptr) { @@ -5399,13 +5864,13 @@ static void blend_read_mdisps(BlendDataReader *reader, /* this calculation is only correct for loop mdisps; * if loading pre-BMesh face mdisps this will be * overwritten with the correct value in - * #bm_corners_to_loops() */ + * bm_corners_to_loops() */ float gridsize = sqrtf(mdisps[i].totdisp); mdisps[i].level = int(logf(gridsize - 1.0f) / float(M_LN2)) + 1; } if (BLO_read_requires_endian_switch(reader) && (mdisps[i].disps)) { - /* #DNA_struct_switch_endian doesn't do endian swap for `(*disps)[]` */ + /* DNA_struct_switch_endian doesn't do endian swap for `(*disps)[]` */ /* this does swap for data written at #write_mdisps() - `readfile.cc`. */ BLI_endian_switch_float_array(*mdisps[i].disps, mdisps[i].totdisp * 3); } diff --git a/source/blender/blenkernel/intern/dyntopo.cc b/source/blender/blenkernel/intern/dyntopo.cc new file mode 100644 index 00000000000..afb33cdbee9 --- /dev/null +++ b/source/blender/blenkernel/intern/dyntopo.cc @@ -0,0 +1,4187 @@ +#include "MEM_guardedalloc.h" + +#include "DNA_customdata_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" + +#include "BLI_alloca.h" +#include "BLI_array.hh" +#include "BLI_asan.h" +#include "BLI_bitmap.h" +#include "BLI_buffer.h" +#include "BLI_compiler_attrs.h" +#include "BLI_compiler_compat.h" +#include "BLI_ghash.h" +#include "BLI_heap.h" +#include "BLI_heap_minmax.hh" +#include "BLI_heap_simple.h" +#include "BLI_index_range.hh" +#include "BLI_linklist.h" +#include "BLI_map.hh" +#include "BLI_math_base_safe.h" +#include "BLI_math_geom.h" +#include "BLI_math_matrix.h" +#include "BLI_math_vector.h" +#include "BLI_math_vector_types.hh" +#include "BLI_memarena.h" +#include "BLI_rand.h" +#include "BLI_rand.hh" +#include "BLI_set.hh" +#include "BLI_task.h" +#include "BLI_task.hh" +#include "BLI_timeit.hh" +#include "BLI_utildefines.h" +#include "BLI_vector.hh" + +#include "BLI_time.h" +#include "atomic_ops.h" + +#include "BKE_customdata.hh" +#include "BKE_dyntopo.hh" +#include "BKE_paint.hh" +#include "BKE_pbvh_api.hh" +#include "BKE_sculpt.hh" + +#include "bmesh.hh" +#include "bmesh_log.hh" + +#include "dyntopo_intern.hh" +#include "pbvh_intern.hh" + +#include +#include + +#define CLEAR_TAGS_IN_THREAD + +#define EDGE_QUEUE_FLAG BM_ELEM_TAG + +using blender::float2; +using blender::float3; +using blender::float4; +using blender::IndexRange; +using blender::Map; +using blender::Set; +using blender::Vector; + +namespace blender::bke::dyntopo { + +using namespace blender::bke::sculpt; + +/* Executes a simple pointer swap; + * if an element ID attribute exists (cd_id_offset is not -1) + * it will unswap IDs. + */ +static void bmesh_swap_data_simple(CustomData * /*data*/, void **block1, void **block2, int cd_id) +{ + std::swap(*block1, *block2); + + /* Unswap element IDs if they exist. */ + if (cd_id != -1 && *block1 && *block2) { + int *id1 = static_cast(POINTER_OFFSET(*block1, cd_id)); + int *id2 = static_cast(POINTER_OFFSET(*block2, cd_id)); + + std::swap(*id1, *id2); + } +} + +static void edge_queue_create_local(EdgeQueueContext *eq_ctx, + PBVH *pbvh, + PBVHTopologyUpdateMode local_mode); + +static void surface_smooth_v_safe( + SculptSession *ss, PBVH *pbvh, BMVert *v, float fac, eAttrCorrectMode distort_correction_mode) +{ + float co[3]; + float origco[3], origco1[3]; + float origno1[3]; + float tan[3]; + float tot = 0.0; + + PBVH_CHECK_NAN(v->co); + + Vector loops; + Vector ws; + + auto addblock = [&](BMEdge *e, float w) { + if (!e->l) { + return; + } + + BMLoop *l = e->l; + do { + BMLoop *l2 = l->v != v ? l : l->next; + + loops.append(l2); + ws.append(w); + } while ((l = l->radial_next) != e->l); + }; + + PBVHVertRef vertex = {reinterpret_cast(v)}; + if (stroke_id_test(ss, vertex, STROKEID_USER_ORIGINAL)) { + copy_v3_v3(origco1, v->co); + copy_v3_v3(origno1, v->no); + } + else { + copy_v3_v3(origco1, blender::bke::paint::vertex_attr_ptr(vertex, ss->attrs.orig_co)); + copy_v3_v3(origno1, blender::bke::paint::vertex_attr_ptr(vertex, ss->attrs.orig_no)); + } + + zero_v3(co); + zero_v3(origco); + + /* This is a manual edge walk. */ + + BMEdge *e = v->e; + if (!e) { + return; + } + + if (pbvh_boundary_needs_update_bmesh(pbvh, v)) { + pbvh_check_vert_boundary_bmesh(pbvh, v); + } + + int boundmask = SCULPTVERT_SMOOTH_BOUNDARY; + int cornermask = SCULPTVERT_SMOOTH_CORNER; + + int boundflag = BM_ELEM_CD_GET_INT(v, pbvh->cd_boundary_flag); + int bound1 = boundflag & boundmask; + + if (boundflag & SCULPT_BOUNDARY_MESH) { + return; + } + if (boundflag & (cornermask | SCULPT_BOUNDARY_SHARP_ANGLE)) { + return; + } + + if (bound1) { + fac *= 0.1; + } + + do { + BMVert *v2 = e->v1 == v ? e->v2 : e->v1; + PBVHVertRef vertex2 = {reinterpret_cast(v2)}; + + if (BM_ELEM_CD_GET_INT(e, pbvh->cd_edge_boundary) & SCULPT_BOUNDARY_SHARP_ANGLE) { + return; + } + + float w; + + if (e->l && e->l->f->len == 3) { + BMLoop *l = e->l; + float w1 = area_tri_v3(l->v->co, l->next->v->co, l->prev->v->co); + + if (l->radial_next != l) { + l = l->radial_next; + float w2 = area_tri_v3(l->v->co, l->next->v->co, l->prev->v->co); + w = (w1 + w2) * 0.5f; + } + else { /* Backup weight if bad areas */ + w = len_squared_v3v3(e->v1->co, e->v2->co); + } + } + else { /* Backup weight if bad areas */ + w = len_squared_v3v3(e->v1->co, e->v2->co); + } + /* Note: we can't validate the boundary flags from with a thread + * so they may not be up to date. + */ + + int boundflag2 = BM_ELEM_CD_GET_INT(v2, pbvh->cd_boundary_flag); + int bound2 = boundflag2 & boundmask; + + if (bound1 && !bound2) { + continue; + } + + addblock(e, w); + + sub_v3_v3v3(tan, v2->co, v->co); + + float d = dot_v3v3(tan, v->no); + madd_v3_v3fl(tan, v->no, -d * 0.95f); + madd_v3_v3fl(co, tan, w); + + float *origco2; + if (!stroke_id_test_no_update(ss, vertex2, STROKEID_USER_ORIGINAL)) { + origco2 = blender::bke::paint::vertex_attr_ptr(vertex2, ss->attrs.orig_co); + } + else { + origco2 = v2->co; + } + + sub_v3_v3v3(tan, origco2, origco1); + d = dot_v3v3(tan, origno1); + madd_v3_v3fl(tan, origno1, -d * 0.95f); + madd_v3_v3fl(origco, tan, w); + + tot += w; + + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + if (tot == 0.0f) { + return; + } + + float startco[3]; + float startno[3]; + if (distort_correction_mode) { + copy_v3_v3(startco, v->co); + copy_v3_v3(startno, v->no); + } + + mul_v3_fl(co, 1.0f / tot); + mul_v3_fl(origco, 1.0f / tot); + + volatile float x = v->co[0], y = v->co[1], z = v->co[2]; + volatile float nx = x + co[0] * fac, ny = y + co[1] * fac, nz = z + co[2] * fac; + + /* Conflicts here should be pretty rare. */ + atomic_cas_float(&v->co[0], x, nx); + atomic_cas_float(&v->co[1], y, ny); + atomic_cas_float(&v->co[2], z, nz); + + PBVH_CHECK_NAN(v->co); + + /* + * Use reprojection for non-UV attributes. UV attributes + * use blender::bke::sculpt::interp_face_corners using + * the weights we built earlier. + */ + /* Reproject attributes. */ + if (distort_correction_mode) { + BKE_sculpt_reproject_cdata(ss, vertex, startco, startno, ss->distort_correction_mode); + + if (distort_correction_mode & UNDISTORT_RELAX_UVS) { + blender::bke::sculpt::interp_face_corners( + pbvh, vertex, loops, ws, fac, pbvh->cd_boundary_flag); + } + } + + PBVH_CHECK_NAN(v->co); + + float *start_origco = blender::bke::paint::vertex_attr_ptr(vertex, ss->attrs.orig_co); + + /* Conflicts here should be pretty rare. */ + x = start_origco[0]; + y = start_origco[1]; + z = start_origco[2]; + + nx = x + origco[0] * fac; + ny = y + origco[1] * fac; + nz = z + origco[2] * fac; + + atomic_cas_float(&start_origco[0], x, nx); + atomic_cas_float(&start_origco[1], y, ny); + atomic_cas_float(&start_origco[2], z, nz); + + PBVH_CHECK_NAN(start_origco); + PBVH_CHECK_NAN(v->co); + // atomic_cas_int32(&mv1->stroke_id, stroke_id, pbvh->stroke_id); +} + +/****************************** EdgeQueue *****************************/ + +static float maskcb_get(EdgeQueueContext *eq_ctx, BMVert *v1, BMVert *v2) +{ + if (eq_ctx->mask_cb) { + PBVHVertRef sv1 = {(intptr_t)v1}; + PBVHVertRef sv2 = {(intptr_t)v2}; + + float w1 = eq_ctx->mask_cb(sv1, eq_ctx->mask_cb_data); + float w2 = eq_ctx->mask_cb(sv2, eq_ctx->mask_cb_data); + + return min_ff(w1, w2); + } + + return 1.0f; +} + +#if 0 +static float get_cross_edge_len(BMEdge *e) +{ + BMLoop *l = e->l; + + float cross = 0.0f; + float tot = 0.0f; + do { + float l1 = len_squared_v3v3(l->next->e->v1->co, l->next->e->v2->co); + float l2 = len_squared_v3v3(l->prev->e->v1->co, l->prev->e->v2->co); + + cross += l1 + l2; + tot += 2.0f; + } while ((l = l->radial_next) != e->l); + + if (tot > 0.0f) { + cross /= tot; + } + + return cross; +} +#endif + +BLI_INLINE float calc_weighted_length(EdgeQueueContext *eq_ctx, + BMVert *v1, + BMVert *v2, + WeightMode mode) +{ + float w = 1.0 - maskcb_get(eq_ctx, v1, v2); + float len = len_squared_v3v3(v1->co, v2->co); + + switch (mode) { + case SPLIT: { + w = 1.0 + w * float(mode); + return len > eq_ctx->limit_len_max_sqr ? len * w * w : len; + } + case COLLAPSE: { + return len < eq_ctx->limit_len_min_sqr ? + len + eq_ctx->limit_len_min_sqr * 1.0f * powf(w, 5.0) : + len; + } + case NONE: /* Avoid compiler warnings. */ + break; + } + + BLI_assert_unreachable(); + return 0.0f; +} + +static WeightMode edge_queue_test(EdgeQueueContext *eq_ctx, PBVH * /*pbvh*/, BMEdge *e, float *r_w) +{ + float len1 = calc_weighted_length(eq_ctx, e->v1, e->v2, SPLIT); + if ((eq_ctx->mode & PBVH_Subdivide) && len1 > eq_ctx->limit_len_max_sqr) { + if (r_w) { + *r_w = len1; + } + return SPLIT; + } + + float len2 = calc_weighted_length(eq_ctx, e->v1, e->v2, COLLAPSE); + if ((eq_ctx->mode & PBVH_Collapse) && len2 < eq_ctx->limit_len_min_sqr) { + if (r_w) { + *r_w = len2; + } + return COLLAPSE; + } + + return NONE; +} + +void EdgeQueueContext::surface_smooth(BMVert *v, float fac) +{ + surface_smooth_v_safe(ss, pbvh, v, fac, distort_correction_mode); +} + +void EdgeQueueContext::insert_edge(BMEdge *e, float w, WeightMode /*mode*/) +{ + if (!(e->head.hflag & EDGE_QUEUE_FLAG)) { +#ifdef DYNTOPO_USE_SEP_HEAPS + if (mode == COLLAPSE) { + min_heap.insert(e, w); + } + else { + max_heap.insert(e, w); + } +#else + edge_heap.insert(w, e); +#endif + e->head.hflag |= EDGE_QUEUE_FLAG; + + if (ignore_loop_data) { + return; + } + + /* Log UVs. */ + if (e->l) { + BMLoop *l = e->l; + do { + int ni = BM_ELEM_CD_GET_INT(l->f, cd_face_node_offset); + PBVHNode *node = BKE_pbvh_get_node_leaf_safe(pbvh, ni); + + /* Check if split_edge_add_recursive has wandered outside + * the set of PBVH_UpdateTopology flagged nodes. + */ + if (node && !(node->flag & PBVH_UpdateTopology)) { + BM_log_face_if_modified(bm, pbvh->bm_log, l->f); + } + } while ((l = l->radial_next) != e->l); + } + } +} + +void EdgeQueueContext::insert_val34_vert(BMVert *v) +{ + if (!v->e) { + return; + } + + used_verts.append(v); +} + +/* + Profiling revealed the accurate distance to tri in blenlib was too slow, + so we use a simpler version here + */ +/* reduce script + +on factor; +off period; + +load_package "avector"; + +comment: origin at p; + +p := avec(0, 0, 0); +n :=- avec(nx, ny, nz); +v1 := avec(v1x, v1y, v1z); +v2 := avec(v2x, v2y, v2z); +v3 := avec(v3x, v3y, v3z); + +comment: -((p - v1) dot n);simplified to this; +fac := v1 dot n; + +co := fac*n; + +a := co - v1; +b := co - v2; +c := co - v3; + +a := v1; +b := v2; +c := v3; + +t1 := a cross b; +t2 := b cross c; +t3 := c cross a; + +on fort; +w1 := t1 dot n; +w2 := t2 dot n; +w3 := t3 dot n; +off fort; + +inside := sign(a dot n) + sign(b dot n) + sign(c dot n); + + +*/ +static bool point_in_tri_v3(float p[3], float v1[3], float v2[3], float v3[3], float n[3]) +{ + float t1[3], t2[3], t3[3]; + sub_v3_v3v3(t1, v1, p); + sub_v3_v3v3(t2, v2, p); + sub_v3_v3v3(t3, v3, p); + + float c1[3], c2[3], c3[3]; + cross_v3_v3v3(c1, t1, t2); + cross_v3_v3v3(c2, t2, t3); + cross_v3_v3v3(c3, t3, t1); + + bool w1 = dot_v3v3(c1, n) >= 0.0f; + bool w2 = dot_v3v3(c2, n) >= 0.0f; + bool w3 = dot_v3v3(c3, n) >= 0.0f; + + return w1 == w2 && w2 == w3; + +#if 0 + const float nx = n[0], ny = n[1], nz = n[2]; + + float v1x = v1[0] - p[0], v1y = v1[1] - p[1], v1z = v1[2] - p[2]; + float v2x = v2[0] - p[0], v2y = v2[1] - p[1], v2z = v2[2] - p[2]; + float v3x = v3[0] - p[0], v3y = v3[1] - p[1], v3z = v3[2] - p[2]; + + const float w1 = -(nx * v1y * v2z - nx * v1z * v2y - ny * v1x * v2z + ny * v1z * v2x + + nz * v1x * v2y - nz * v1y * v2x); + const float w2 = -(nx * v2y * v3z - nx * v2z * v3y - ny * v2x * v3z + ny * v2z * v3x + + nz * v2x * v3y - nz * v2y * v3x); + const float w3 = nx * v1y * v3z - nx * v1z * v3y - ny * v1x * v3z + ny * v1z * v3x + + nz * v1x * v3y - nz * v1y * v3x; + return !((w1 >= 0.0f) && (w2 >= 0.0f) && (w3 >= 0.0f)); +#endif +} + +float dist_to_tri_sphere_simple(float p[3], float v1[3], float v2[3], float v3[3], float n[3]) +{ +#if 0 + float a = len_squared_v3v3(p, v1); + float b = len_squared_v3v3(p, v2); + float c = len_squared_v3v3(p, v3); + + float dis = min_ff(min_ff(a, b), c); + return dis; +#else + float co[3]; + float t1[3], t2[3], t3[3]; + + if (dot_v3v3(n, n) == 0.0f) { + normal_tri_v3(n, v1, v2, v3); + } + + if (point_in_tri_v3(p, v1, v2, v3, n)) { + sub_v3_v3v3(co, p, v2); + + float dist = dot_v3v3(co, n); + return dist * dist; + } + + sub_v3_v3v3(co, p, v1); + madd_v3_v3fl(co, n, -dot_v3v3(n, co)); + + sub_v3_v3v3(t1, v1, co); + sub_v3_v3v3(t2, v2, co); + sub_v3_v3v3(t3, v3, co); + + float dis = len_squared_v3v3(p, v1); + dis = fmin(dis, len_squared_v3v3(p, v2)); + dis = fmin(dis, len_squared_v3v3(p, v3)); + + add_v3_v3v3(co, v1, v2); + mul_v3_fl(co, 0.5f); + dis = fmin(dis, len_squared_v3v3(p, co)); + + add_v3_v3v3(co, v2, v3); + mul_v3_fl(co, 0.5f); + dis = fmin(dis, len_squared_v3v3(p, co)); + + add_v3_v3v3(co, v3, v1); + mul_v3_fl(co, 0.5f); + dis = fmin(dis, len_squared_v3v3(p, co)); + + add_v3_v3v3(co, v1, v2); + add_v3_v3(co, v3); + mul_v3_fl(co, 1.0f / 3.0f); + dis = fmin(dis, len_squared_v3v3(p, co)); + + return dis; +#endif +} + +static void add_split_edge_recursive( + EdgeQueueContext *eq_ctx, BMLoop *l_edge, const float len_sq, float limit_len, int depth) +{ + if (depth > DEPTH_START_LIMIT && eq_ctx->use_view_normal) { + if (dot_v3v3(l_edge->f->no, eq_ctx->view_normal) < 0.0f) { + return; + } + } + + // if (!skinny_bad_edge(l_edge->e)) { + eq_ctx->insert_edge(l_edge->e, len_sq, SPLIT); + //} + + if ((l_edge->radial_next != l_edge)) { + const float len_sq_cmp = len_sq * EVEN_EDGELEN_THRESHOLD; + + limit_len *= EVEN_GENERATION_SCALE; + const float limit_len_sq = square_f(limit_len); + + BMLoop *l_iter = l_edge; + do { + BMLoop *l_adjacent[2] = {l_iter->next, l_iter->prev}; + for (int i = 0; i < (int)ARRAY_SIZE(l_adjacent); i++) { + if (l_adjacent[i]->e->head.hflag & EDGE_QUEUE_FLAG) { + continue; + } + + float len_sq_other = calc_weighted_length( + eq_ctx, l_adjacent[i]->e->v1, l_adjacent[i]->e->v2, SPLIT); + + bool insert_ok = len_sq_other > max_ff(len_sq_cmp, limit_len_sq); + if (!insert_ok) { + continue; + } + + add_split_edge_recursive( + eq_ctx, l_adjacent[i]->radial_next, len_sq_other, limit_len, depth + 1); + } + } while ((l_iter = l_iter->radial_next) != l_edge); + } +} + +struct EdgeQueueThreadData { + PBVH *pbvh = nullptr; + PBVHNode *node = nullptr; + Vector edges; + EdgeQueueContext *eq_ctx = nullptr; + int size = 0; + bool is_collapse = false; + int seed = 0; +}; + +static void edge_thread_data_insert(EdgeQueueThreadData *tdata, BMEdge *e) +{ + tdata->edges.append(e); + + BMElem elem; + memcpy(&elem, (BMElem *)e, sizeof(BMElem)); + + elem.head.hflag = e->head.hflag | EDGE_QUEUE_FLAG; + int64_t iold = *((int64_t *)&e->head.index); + int64_t inew = *((int64_t *)&elem.head.index); + + atomic_cas_int64((int64_t *)&e->head.index, iold, inew); +} + +static void add_split_edge_recursive_threaded(EdgeQueueThreadData *tdata, + BMLoop *l_edge, + BMLoop *l_end, + const float len_sq, + float limit_len, + int depth, + bool insert) +{ + BLI_assert(len_sq > square_f(limit_len)); + + BMLoop *l = l_edge; + int count = 0; + do { + if (count++ > 5) { + printf("%s: topology error: highly non-manifold edge %p\n", __func__, l_edge->e); + BM_vert_select_set(tdata->pbvh->header.bm, l_edge->e->v1, true); + BM_vert_select_set(tdata->pbvh->header.bm, l_edge->e->v2, true); + BM_edge_select_set(tdata->pbvh->header.bm, l_edge->e, true); + return; + } + } while ((l = l->radial_next) != l_edge); + + if (l_edge->e->head.hflag & EDGE_QUEUE_FLAG) { + return; + } + +#ifdef USE_EDGEQUEUE_FRONTFACE + if (depth > DEPTH_START_LIMIT && tdata->eq_ctx->use_view_normal) { + if (dot_v3v3(l_edge->f->no, tdata->eq_ctx->view_normal) < 0.0f) { + return; + } + } +#endif + + if (insert) { + edge_thread_data_insert(tdata, l_edge->e); + } + + if ((l_edge->radial_next != l_edge)) { + const float len_sq_cmp = len_sq * EVEN_EDGELEN_THRESHOLD; + + limit_len *= EVEN_GENERATION_SCALE; + const float limit_len_sq = square_f(limit_len); + + BMLoop *l_iter = l_edge; + do { + BMLoop *l_adjacent[2] = {l_iter->next, l_iter->prev}; + for (int i = 0; i < (int)ARRAY_SIZE(l_adjacent); i++) { + float len_sq_other = calc_weighted_length( + tdata->eq_ctx, l_adjacent[i]->e->v1, l_adjacent[i]->e->v2, SPLIT); + + bool insert_ok = len_sq_other > max_ff(len_sq_cmp, limit_len_sq); +#ifdef EVEN_NO_TEST_DEPTH_LIMIT + if (!insert_ok && depth >= EVEN_NO_TEST_DEPTH_LIMIT) { + continue; + } +#else + if (!insert_ok) { + continue; + } +#endif + + add_split_edge_recursive_threaded(tdata, + l_adjacent[i]->radial_next, + l_adjacent[i], + len_sq_other, + limit_len, + depth + 1, + insert_ok); + } + } while ((l_iter = l_iter->radial_next) != l_end); + } +} + +static void unified_edge_queue_task_cb(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict /*tls*/) +{ + blender::RandomNumberGenerator rand(uint32_t(n + BLI_time_now_seconds() * 100000.0f)); + EdgeQueueThreadData *tdata = ((EdgeQueueThreadData *)userdata) + n; + PBVH *pbvh = tdata->pbvh; + PBVHNode *node = tdata->node; + EdgeQueueContext *eq_ctx = tdata->eq_ctx; + + BKE_pbvh_bmesh_check_tris(pbvh, node); + int ni = int(node - &pbvh->nodes[0]); + + const char facetag = BM_ELEM_TAG_ALT; + +/* + * Clear edge flags. + * + * We care more about convergence to accurate results + * then accuracy in any individual runs. Profiling + * has shown this loop overwhelms the L3 cache, + * so randomly skip bits of it. + * + * Unfortunately profiling has shown it is necassary to clear + * the flags here and not in the main thread. + */ +#ifdef CLEAR_TAGS_IN_THREAD + for (BMFace *f : *node->bm_faces) { + BMLoop *l = f->l_first; + + /* Note that f itself is owned by this node. */ + f->head.hflag &= ~facetag; + + /* Stochastically skip faces. */ + if (rand.get_float() > 0.8f) { + continue; + } + + do { + /* Kind of tricky to atomicly update flags here. We probably + * don't need to do this on x86, but I'm not sure about ARM. + */ + BMEdge edge = *l->e; + edge.head.hflag &= ~EDGE_QUEUE_FLAG; + + int64_t *t1 = (int64_t *)&edge.head.index; + int64_t *t2 = (int64_t *)&l->e->head.index; + + atomic_cas_int64(t2, *t2, *t1); + + l = l->next; + } while (l != f->l_first); + } +#endif + + PBVHTriBuf *tribuf = node->tribuf; + for (int i = 0; i < node->tribuf->tris.size(); i++) { + PBVHTri *tri = &node->tribuf->tris[i]; + BMFace *f = (BMFace *)tri->f.i; + + if (f->head.hflag & facetag) { + continue; + } + +#ifdef USE_EDGEQUEUE_FRONTFACE + if (eq_ctx->use_view_normal) { + if (dot_v3v3(f->no, eq_ctx->view_normal) < 0.0f) { + continue; + } + } +#endif + + BMVert *vs[3] = {(BMVert *)tribuf->verts[tri->v[0]].i, + (BMVert *)tribuf->verts[tri->v[1]].i, + (BMVert *)tribuf->verts[tri->v[2]].i}; + if (eq_ctx->brush_tester->tri_in_range(vs, f->no)) { + f->head.hflag |= facetag; + + /* Check each edge of the face. */ + BMLoop *l_first = BM_FACE_FIRST_LOOP(f); + BMLoop *l_iter = l_first; + do { + /* Are we owned by this node? if so, make sure origdata is up to date. */ + if (BM_ELEM_CD_GET_INT(l_iter->v, pbvh->cd_vert_node_offset) == ni) { + BKE_pbvh_bmesh_check_origdata(eq_ctx->ss, l_iter->v, pbvh->stroke_id); + } + + /* Try to improve convergence by applying a small amount of smoothing to topology, + * but tangentially to surface. We can stochastically skip this and still get the + * benefit to convergence. + */ + if (BM_ELEM_CD_GET_INT(l_iter->v, pbvh->cd_vert_node_offset) == ni) { + eq_ctx->stochastic_smooth(l_iter->v); + } + + float w = 0.0f; + WeightMode mode = edge_queue_test(eq_ctx, pbvh, l_iter->e, &w); + + /* Subdivide walks the mesh a bit for better transitions in the topology. */ + if (mode == SPLIT) { + add_split_edge_recursive_threaded( + tdata, l_iter->radial_next, l_iter, w, eq_ctx->limit_len_max, 0, true); + } + else if (mode == COLLAPSE) { + edge_thread_data_insert(tdata, l_iter->e); + } + } while ((l_iter = l_iter->next) != l_first); + } + } +} + +bool check_face_is_tri(PBVH *pbvh, BMFace *f) +{ +#if DYNTOPO_DISABLE_FLAG & DYNTOPO_DISABLE_TRIANGULATOR + return true; +#endif + + if (f->len == 3) { + return true; + } + + if (f->len < 3) { + printf("pbvh had < 3 vert face!\n"); + BKE_pbvh_bmesh_remove_face(pbvh, f, false); + return false; + } + + LinkNode *dbl = nullptr; + + Vector fs; + Vector es; + + BMLoop *l = f->l_first; + do { + validate_vert(pbvh, l->v, CHECK_VERT_ALL); + dyntopo_add_flag(pbvh, l->v, SCULPTFLAG_NEED_VALENCE); + + if (l->e->head.index == -1) { + l->e->head.index = 0; + } + } while ((l = l->next) != f->l_first); + + // BKE_pbvh_bmesh_remove_face(pbvh, f, true); + pbvh_bmesh_face_remove(pbvh, f, false, true, true); + BM_log_face_removed(pbvh->header.bm, pbvh->bm_log, f); + BM_idmap_release(pbvh->bm_idmap, f, true); + + int len = (f->len - 2) * 3; + + fs.resize(len); + es.resize(len); + + int totface = 0; + int totedge = 0; + MemArena *arena = nullptr; + struct Heap *heap = nullptr; + + arena = BLI_memarena_new(512, "ngon arena"); + heap = BLI_heap_new(); + + BM_face_triangulate(pbvh->header.bm, + f, + fs.data(), + &totface, + es.data(), + &totedge, + &dbl, + MOD_TRIANGULATE_QUAD_FIXED, + MOD_TRIANGULATE_NGON_BEAUTY, + false, + arena, + heap); + + while (totface && dbl) { + BMFace *f2 = (BMFace *)dbl->link; + LinkNode *next = dbl->next; + + for (int i = 0; i < totface; i++) { + if (fs[i] == f2) { + fs[i] = nullptr; + } + } + + if (f == f2) { + BM_log_face_added(pbvh->header.bm, pbvh->bm_log, f); + BM_log_face_removed_no_check(pbvh->header.bm, pbvh->bm_log, f); + f = nullptr; + } + + BMLoop *l = f2->l_first; + do { + pbvh_boundary_update_bmesh(pbvh, l->v); + pbvh_boundary_update_bmesh(pbvh, l->e); + dyntopo_add_flag(pbvh, l->v, SCULPTFLAG_NEED_VALENCE); + } while ((l = l->next) != f2->l_first); + + BM_idmap_release(pbvh->bm_idmap, static_cast(dbl->link), true); + BM_face_kill(pbvh->header.bm, f2); + + MEM_freeN(dbl); + dbl = next; + } + + for (int i = 0; i < totface; i++) { + BMFace *f2 = fs[i]; + + if (!f2) { + continue; + } + + BMLoop *l = f2->l_first; + do { + pbvh_boundary_update_bmesh(pbvh, l->v); + pbvh_boundary_update_bmesh(pbvh, l->e); + dyntopo_add_flag(pbvh, l->v, SCULPTFLAG_NEED_VALENCE); + validate_edge(pbvh, l->e); + } while ((l = l->next) != f2->l_first); + } + + for (int i = 0; i < totface; i++) { + BMFace *f2 = fs[i]; + + if (!f2) { + continue; + } + + if (f == f2) { + printf("%s: error\n", __func__); + continue; + } + + /* Detect new edges. */ + BMLoop *l = f2->l_first; + do { + pbvh_boundary_update_bmesh(pbvh, l->v); + pbvh_boundary_update_bmesh(pbvh, l->e); + + if (l->e->head.index == -1) { + *BM_ELEM_CD_PTR(l->e, + pbvh->cd_edge_boundary) &= ~(SCULPT_BOUNDARY_UV | SCULPT_CORNER_UV); + + BM_log_edge_added(pbvh->header.bm, pbvh->bm_log, l->e); + dyntopo_add_flag(pbvh, l->v, SCULPTFLAG_NEED_VALENCE); + dyntopo_add_flag(pbvh, l->next->v, SCULPTFLAG_NEED_VALENCE); + validate_edge(pbvh, l->e); + l->e->head.index = 0; + } + } while ((l = l->next) != f2->l_first); + + validate_face(pbvh, f2, CHECK_FACE_MANIFOLD); + + BKE_pbvh_bmesh_add_face(pbvh, f2, false, true); + // BM_log_face_post(pbvh->bm_log, f2); + BM_log_face_added(pbvh->header.bm, pbvh->bm_log, f2); + } + + if (f) { + BKE_pbvh_bmesh_add_face(pbvh, f, false, true); + validate_face(pbvh, f, CHECK_FACE_MANIFOLD); + BM_log_face_added(pbvh->header.bm, pbvh->bm_log, f); + } + + if (arena) { + BLI_memarena_free(arena); + } + + if (heap) { + BLI_heap_free(heap, nullptr); + } + + pbvh_bmesh_check_nodes(pbvh); + + return false; +} + +bool destroy_nonmanifold_fins(PBVH *pbvh, BMEdge *e_root) +{ +#if !(DYNTOPO_DISABLE_FLAG & DYNTOPO_DISABLE_FIN_REMOVAL) + static int max_faces = 64; + Vector stack; + + BMLoop *l = e_root->l; + Vector ls; + Vector minfs; + + if (!l) { + return false; + } + + do { + ls.append(l); + } while ((l = l->radial_next) != e_root->l); + + for (int i = 0; i < ls.size(); i++) { + Set visit; + + BMLoop *l = ls[i]; + BMFace *f = l->f; + Vector fs2; + stack.clear(); + + stack.append(f); + fs2.append(f); + + visit.add(f); + + bool bad = false; + + while (stack.size() > 0) { + f = stack.pop_last(); + BMLoop *l = f->l_first; + + do { + if (l->radial_next == l || l->radial_next->radial_next != l) { + continue; + } + + BMFace *f2 = l->radial_next->f; + + if (visit.add(f2)) { + if (fs2.size() > max_faces) { + bad = true; + break; + } + + stack.append(f2); + fs2.append(f2); + } + } while ((l = l->next) != f->l_first); + + if (bad) { + break; + } + } + + if (!bad && fs2.size() && (minfs.size() == 0 || fs2.size() < minfs.size())) { + minfs = fs2; + } + } + + int node_updateflag = PBVH_UpdateDrawBuffers | PBVH_UpdateBB | PBVH_UpdateTriAreas; + node_updateflag = node_updateflag | PBVH_UpdateNormals | PBVH_UpdateTris | + PBVH_RebuildDrawBuffers; + + if (!minfs.size()) { + return false; + } + + const int updateflag = SCULPTFLAG_NEED_VALENCE; + + // printf("manifold fin size: %d\n", (int)minfs.size()); + const int tag = BM_ELEM_TAG_ALT; + + for (int i = 0; i < minfs.size(); i++) { + BMFace *f = minfs[i]; + + BMLoop *l = f->l_first; + do { + BMLoop *l2 = l; + do { + BMLoop *l3 = l2; + do { + l3->v->head.hflag &= ~tag; + l3->e->head.hflag &= ~tag; + } while ((l3 = l3->next) != l2); + } while ((l2 = l2->radial_next) != l); + + l->v->head.hflag &= ~tag; + l->e->head.hflag &= ~tag; + } while ((l = l->next) != f->l_first); + } + + Vector vs; + Vector es; + + for (int i = 0; i < minfs.size(); i++) { + BMFace *f = minfs[i]; + + BMLoop *l = f->l_first; + do { + if (!(l->v->head.hflag & tag)) { + l->v->head.hflag |= tag; + pbvh_boundary_update_bmesh(pbvh, l->v); + pbvh_boundary_update_bmesh(pbvh, l->e); + dyntopo_add_flag(pbvh, l->v, updateflag); + vs.append(l->v); + } + + if (!(l->e->head.hflag & tag)) { + l->e->head.hflag |= tag; + es.append(l->e); + } + } while ((l = l->next) != f->l_first); + } + + for (int i = 0; i < minfs.size(); i++) { + for (int j = 0; j < minfs.size(); j++) { + if (i != j && minfs[i] == minfs[j]) { + printf("%s: duplicate faces\n", __func__); + continue; + } + } + } + + for (int i = 0; i < minfs.size(); i++) { + BMFace *f = minfs[i]; + + if (f->head.htype != BM_FACE) { + printf("%s: corruption!\n", __func__); + continue; + } + + int ni = BM_ELEM_CD_GET_INT(f, pbvh->cd_face_node_offset); + if (ni >= 0 && ni < pbvh->nodes.size()) { + pbvh->nodes[ni].flag |= (PBVHNodeFlags)node_updateflag; + } + + pbvh_bmesh_face_remove(pbvh, f, true, true, false); + BM_idmap_release(pbvh->bm_idmap, f, true); + BM_face_kill(pbvh->header.bm, f); + } + + for (int i = 0; i < es.size(); i++) { + BMEdge *e = es[i]; + + if (!e->l) { + BM_log_edge_removed(pbvh->header.bm, pbvh->bm_log, e); + BM_idmap_release(pbvh->bm_idmap, e, true); + BM_edge_kill(pbvh->header.bm, e); + } + else { + pbvh_boundary_update_bmesh(pbvh, e); + + pbvh_boundary_update_bmesh(pbvh, e->v1); + dyntopo_add_flag(pbvh, e->v1, updateflag); + + pbvh_boundary_update_bmesh(pbvh, e->v2); + dyntopo_add_flag(pbvh, e->v2, updateflag); + } + } + + for (int i = 0; i < vs.size(); i++) { + BMVert *v = vs[i]; + + if (!v->e) { + pbvh_bmesh_vert_remove(pbvh, v); + + BM_log_vert_removed(pbvh->header.bm, pbvh->bm_log, v); + BM_idmap_release(pbvh->bm_idmap, v, true); + BM_vert_kill(pbvh->header.bm, v); + } + else { + pbvh_boundary_update_bmesh(pbvh, v->e); + + pbvh_boundary_update_bmesh(pbvh, v); + dyntopo_add_flag(pbvh, v, updateflag); + } + } + + pbvh_bmesh_check_nodes(pbvh); + + return true; +#else + return false; +#endif +} + +bool check_for_fins(PBVH *pbvh, BMVert *v) +{ + BMEdge *e = v->e; + if (!e) { + return false; + } + + do { + if (!e) { + printf("%s: e was nullptr\n", __func__); + break; + } + if (e->l) { + BMLoop *l = e->l->f->l_first; + + do { + if (l != l->radial_next && l != l->radial_next->radial_next) { + if (destroy_nonmanifold_fins(pbvh, e)) { + return true; + } + } + } while ((l = l->next) != e->l->f->l_first); + } + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + return false; +} + +bool check_vert_fan_are_tris(PBVH *pbvh, BMVert *v) +{ + static Vector fs; + + /* Prevent pathological allocation thrashing on topology with + * vertices with lots of edges around them by reusing the same + * static local vector, instead of allocating on the stack. + */ + fs.clear(); + + uint8_t *flag = BM_ELEM_CD_PTR(v, pbvh->cd_flag); + if (!(*flag & SCULPTFLAG_NEED_TRIANGULATE)) { + return true; + } + + if (!v->e) { + *flag &= ~SCULPTFLAG_NEED_TRIANGULATE; + return true; + } + + const int tag = BM_ELEM_TAG_ALT; + + BMEdge *e = v->e; + do { + BMLoop *l = e->l; + + if (!l) { + continue; + } + + do { + l->f->head.hflag |= tag; + } while ((l = l->radial_next) != e->l); + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + e = v->e; + do { + BMLoop *l = e->l; + + if (!l) { + continue; + } + + do { + if (l->f->head.hflag & tag) { + l->f->head.hflag &= ~tag; + fs.append(l->f); + } + } while ((l = l->radial_next) != e->l); + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + for (int i = 0; i < fs.size(); i++) { + /* Triangulation can sometimes delete a face. */ + if (!BM_elem_is_free((BMElem *)fs[i], BM_FACE)) { + check_face_is_tri(pbvh, fs[i]); + } + } + + *flag &= ~SCULPTFLAG_NEED_TRIANGULATE; + return false; +} + +/* Create a priority queue containing vertex pairs connected by a long + * edge as defined by PBVH.bm_max_edge_len. + * + * Only nodes marked for topology update are checked, and in those + * nodes only edges used by a face intersecting the (center, radius) + * sphere are checked. + * + * The highest priority (lowest number) is given to the longest edge. + */ +static void unified_edge_queue_create(EdgeQueueContext *eq_ctx, + PBVH *pbvh, + PBVHTopologyUpdateMode local_mode) +{ + if (local_mode) { + edge_queue_create_local(eq_ctx, pbvh, local_mode); + return; + } + +#ifdef USE_EDGEQUEUE_TAG_VERIFY + pbvh_bmesh_edge_tag_verify(pbvh); +#endif + + Vector tdata; + + for (int n = 0; n < pbvh->nodes.size(); n++) { + PBVHNode *node = &pbvh->nodes[n]; + + /* Check leaf nodes marked for topology update */ + bool ok = ((node->flag & PBVH_Leaf) && (node->flag & PBVH_UpdateTopology) && + !(node->flag & PBVH_FullyHidden)); + + if (!ok) { + continue; + } + + EdgeQueueThreadData td = {}; + + td.seed = BLI_thread_rand(0); + td.pbvh = pbvh; + td.node = node; + td.eq_ctx = eq_ctx; + + tdata.append(td); + } + + int count = tdata.size(); + + TaskParallelSettings settings; + + BLI_parallel_range_settings_defaults(&settings); + settings.use_threading = true; + +#ifdef DYNTOPO_NO_THREADING + settings.use_threading = false; +#endif + +#ifndef CLEAR_TAGS_IN_THREAD + for (int i : IndexRange(pbvh->nodes.size())) { + PBVHNode *node = &pbvh->nodes[i]; + + if (!(node->flag & PBVH_Leaf) || !(node->flag & PBVH_UpdateTopology)) { + continue; + } + + for (BMFace *f : *node->bm_faces) { + if (BM_elem_is_free(reinterpret_cast(f), BM_FACE)) { + printf("%s: freed face in node!\n", __func__); + node->bm_faces->remove(f); + + continue; + } + + BMLoop *l = f->l_first; + do { + l->e->head.hflag &= ~EDGE_QUEUE_FLAG; + l->v->head.hflag &= ~BM_ELEM_TAG; + l->f->head.hflag &= ~(BM_ELEM_TAG | BM_ELEM_TAG_ALT); + } while ((l = l->next) != f->l_first); + } + } +#endif + + BLI_task_parallel_range(0, count, (void *)tdata.data(), unified_edge_queue_task_cb, &settings); + + for (int i = 0; i < count; i++) { + for (BMEdge *e : tdata[i].edges) { + e->head.hflag &= ~EDGE_QUEUE_FLAG; + } + } + + Vector verts; + for (int i = 0; i < count; i++) { + for (BMEdge *e : tdata[i].edges) { + if (bm_elem_is_free((BMElem *)e, BM_EDGE)) { + continue; + } + + e->head.hflag &= ~EDGE_QUEUE_FLAG; + + if (e->l && e->l != e->l->radial_next->radial_next) { + /* Delete non-manifold "fins". */ + destroy_nonmanifold_fins(pbvh, e); + + if (bm_elem_is_free((BMElem *)e, BM_EDGE)) { + continue; + } + } + + if (dyntopo_test_flag(pbvh, e->v1, SCULPTFLAG_NEED_VALENCE)) { + BKE_pbvh_bmesh_update_valence(pbvh, {(intptr_t)e->v1}); + } + + if (dyntopo_test_flag(pbvh, e->v2, SCULPTFLAG_NEED_VALENCE)) { + BKE_pbvh_bmesh_update_valence(pbvh, {(intptr_t)e->v2}); + } + + if (eq_ctx->use_view_normal && (dot_v3v3(e->v1->no, eq_ctx->view_normal) < 0.0f && + dot_v3v3(e->v2->no, eq_ctx->view_normal) < 0.0f)) + { + continue; + } + + verts.append(e->v1); + verts.append(e->v2); + + e->v1->head.hflag |= EDGE_QUEUE_FLAG; + e->v2->head.hflag |= EDGE_QUEUE_FLAG; + + float w; + if (WeightMode wmode = edge_queue_test(eq_ctx, pbvh, e, &w)) { + bool insert = wmode == SPLIT && (eq_ctx->mode & PBVH_Subdivide); + insert |= wmode == COLLAPSE && (eq_ctx->mode & PBVH_Collapse); + + if (insert) { + eq_ctx->insert_edge(e, w, wmode); + } + } + } + } + + /* Push a subentry just to be on the safe side w.r.t. element IDs. */ + BM_log_entry_add_delta_set(pbvh->header.bm, pbvh->bm_log); +} + +static void short_edge_queue_task_cb_local(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict /*tls*/) +{ + EdgeQueueThreadData *tdata = ((EdgeQueueThreadData *)userdata) + n; + PBVHNode *node = tdata->node; + EdgeQueueContext *eq_ctx = tdata->eq_ctx; + + for (BMFace *f : *node->bm_faces) { +#ifdef USE_EDGEQUEUE_FRONTFACE + if (eq_ctx->use_view_normal) { + if (dot_v3v3(f->no, eq_ctx->view_normal) < 0.0f) { + continue; + } + } +#endif + + BMVert *vs[3] = {f->l_first->v, f->l_first->next->v, f->l_first->next->next->v}; + if (eq_ctx->brush_tester->tri_in_range(vs, f->no)) { + BMLoop *l = f->l_first; + + do { + edge_thread_data_insert(tdata, l->e); + + } while ((l = l->next) != f->l_first); + } + } +} + +static void edge_queue_create_local(EdgeQueueContext *eq_ctx, + PBVH *pbvh, + PBVHTopologyUpdateMode local_mode) +{ + eq_ctx->local_mode = true; + + Vector tdata; + + for (int n = 0; n < pbvh->nodes.size(); n++) { + PBVHNode *node = &pbvh->nodes[n]; + EdgeQueueThreadData td; + + if ((node->flag & PBVH_Leaf) && (node->flag & PBVH_UpdateTopology) && + !(node->flag & PBVH_FullyHidden)) + { + td.pbvh = pbvh; + td.node = node; + td.is_collapse = local_mode & PBVH_LocalCollapse; + td.eq_ctx = eq_ctx; + + tdata.append(td); + } + } + + int count = tdata.size(); + + TaskParallelSettings settings; + + BLI_parallel_range_settings_defaults(&settings); +#ifdef DYNTOPO_NO_THREADING + settings.use_threading = false; +#endif + + BLI_task_parallel_range( + 0, count, (void *)tdata.data(), short_edge_queue_task_cb_local, &settings); + + Vector lens; + Vector edges; + + for (int i = 0; i < count; i++) { + for (BMEdge *e : tdata[i].edges) { + e->head.hflag &= ~EDGE_QUEUE_FLAG; + } + } + + for (int i = 0; i < count; i++) { + for (BMEdge *e : tdata[i].edges) { + e->v1->head.hflag &= ~BM_ELEM_TAG; + e->v2->head.hflag &= ~BM_ELEM_TAG; + + if (!(e->head.hflag & EDGE_QUEUE_FLAG)) { + edges.append(e); + e->head.hflag |= EDGE_QUEUE_FLAG; + } + } + } + + for (int i = 0; i < edges.size(); i++) { + BMEdge *e = edges[i]; + float len = len_v3v3(e->v1->co, e->v2->co); + + for (int j = 0; j < 2; j++) { + BMVert *v = j ? e->v2 : e->v1; + + if (!(local_mode & PBVH_LocalCollapse)) { + if (!(v->head.hflag & BM_ELEM_TAG)) { + v->head.hflag |= BM_ELEM_TAG; + + if (dyntopo_test_flag(pbvh, v, SCULPTFLAG_NEED_VALENCE)) { + BKE_pbvh_bmesh_update_valence(pbvh, {(intptr_t)v}); + } + + eq_ctx->insert_val34_vert(v); + } + } + } + + e->head.index = i; + lens.append(len); + } + + /* Make sure tags around border edges are unmarked. */ + for (int i = 0; i < edges.size(); i++) { + BMEdge *e = edges[i]; + + for (int j = 0; j < 2; j++) { + BMVert *v1 = j ? e->v2 : e->v1; + BMEdge *e1 = v1->e; + + do { + e1->head.hflag &= ~EDGE_QUEUE_FLAG; + + e1 = BM_DISK_EDGE_NEXT(e1, v1); + } while (e1 != v1->e); + } + } + + /* Re-tag edge list. */ + for (int i = 0; i < edges.size(); i++) { + edges[i]->head.hflag |= EDGE_QUEUE_FLAG; + } + + int totstep = 3; + + /* Blur edge lengths. */ + for (int step = 0; step < totstep; step++) { + for (int i = 0; i < edges.size(); i++) { + BMEdge *e = edges[i]; + + float len = lens[i]; + float totlen = 0.0f; + + for (int j = 0; j < 2; j++) { + BMVert *v1 = j ? e->v2 : e->v1; + BMEdge *e1 = v1->e; + + do { + if (e1->head.hflag & EDGE_QUEUE_FLAG) { + len += lens[e1->head.index]; + totlen += 1.0f; + } + + e1 = BM_DISK_EDGE_NEXT(e1, v1); + } while (e1 != v1->e); + } + + if (totlen != 0.0f) { + len /= totlen; + lens[i] += (len - lens[i]) * 0.5; + } + } + } + + pbvh->header.bm->elem_index_dirty |= BM_EDGE; + + float limit = 0.0f; + float tot = 0.0f; + + for (int i = 0; i < edges.size(); i++) { + BMEdge *e = edges[i]; + + e->head.hflag &= ~EDGE_QUEUE_FLAG; + + pbvh_check_vert_boundary_bmesh(pbvh, e->v1); + pbvh_check_vert_boundary_bmesh(pbvh, e->v2); + + int boundflag1 = BM_ELEM_CD_GET_INT(e->v1, pbvh->cd_boundary_flag); + int boundflag2 = BM_ELEM_CD_GET_INT(e->v2, pbvh->cd_boundary_flag); + + if ((boundflag1 & SCULPTVERT_ALL_CORNER) || (boundflag2 & SCULPTVERT_ALL_CORNER)) { + continue; + } + + if ((boundflag1 & SCULPTVERT_ALL_BOUNDARY) != (boundflag2 & SCULPTVERT_ALL_BOUNDARY)) { + continue; + } + + limit += lens[i]; + tot += 1.0f; + } + + if (tot > 0.0f) { + limit /= tot; + + if (local_mode & PBVH_LocalCollapse) { + eq_ctx->limit_len_min = limit * pbvh->bm_detail_range; + eq_ctx->limit_len_min_sqr = eq_ctx->limit_len_min * eq_ctx->limit_len_min; + } + + if (local_mode & PBVH_LocalSubdivide) { + eq_ctx->limit_len_max = limit; + eq_ctx->limit_len_max_sqr = eq_ctx->limit_len_max * eq_ctx->limit_len_max; + } + } + + for (int i = 0; i < edges.size(); i++) { + BMEdge *e = edges[i]; + + int boundflag1 = BM_ELEM_CD_GET_INT(e->v1, pbvh->cd_boundary_flag); + int boundflag2 = BM_ELEM_CD_GET_INT(e->v2, pbvh->cd_boundary_flag); + + if ((boundflag1 & SCULPTVERT_ALL_CORNER) || (boundflag2 & SCULPTVERT_ALL_CORNER)) { + continue; + } + + if ((boundflag1 & SCULPTVERT_ALL_BOUNDARY) != (boundflag2 & SCULPTVERT_ALL_BOUNDARY)) { + continue; + } + + bool ok = false; + + bool a = eq_ctx->mode & (PBVH_Subdivide | PBVH_LocalSubdivide); + bool b = eq_ctx->mode & (PBVH_Collapse | PBVH_LocalCollapse); + + float len1 = calc_weighted_length(eq_ctx, e->v1, e->v2, COLLAPSE); + float len2 = calc_weighted_length(eq_ctx, e->v1, e->v2, SPLIT); + float w = 0.0f; + + WeightMode mode; + + if (a && b) { + mode = edge_queue_test(eq_ctx, eq_ctx->pbvh, e, &w); +#if 0 + ok = len1 < eq_ctx->limit_len_min_sqr || len1 > eq_ctx->limit_len_max_sqr; + ok = ok || (len2 < pbvh->bm_min_edge_len || len2 > pbvh->bm_max_edge_len); + w = (len1 + len2) * 0.5; +#endif + } + else if (a) { + ok = len1 > eq_ctx->limit_len_max || len1 > pbvh->bm_max_edge_len; + w = len1; + mode = SPLIT; + } + else if (b) { + ok = len2 < eq_ctx->limit_len_min || len2 < pbvh->bm_min_edge_len; + w = len2; + mode = COLLAPSE; + } + + if (!ok) { + continue; + } + + eq_ctx->insert_edge(e, w, mode); + } +} + +static bool cleanup_valence_3_4(EdgeQueueContext *ectx, PBVH *pbvh) +{ + bool modified = false; + const int cd_vert_node = pbvh->cd_vert_node_offset; + int updateflag = SCULPTFLAG_NEED_VALENCE; + + for (BMVert *v : ectx->used_verts) { + if (bm_elem_is_free((BMElem *)v, BM_VERT)) { + continue; + } + + const int n = BM_ELEM_CD_GET_INT(v, cd_vert_node); + if (n == DYNTOPO_NODE_NONE) { + continue; + } + + PBVHVertRef sv = {(intptr_t)v}; + if (!v->e || ectx->mask_cb(sv, ectx->mask_cb_data) < 0.5f) { + continue; + } + + validate_vert(pbvh, v, CHECK_VERT_ALL); + + check_vert_fan_are_tris(pbvh, v); + pbvh_check_vert_boundary_bmesh(pbvh, v); + + validate_vert(pbvh, v, CHECK_VERT_ALL); + + int val = BM_vert_edge_count(v); + + if (val != 4 && val != 3) { + continue; + } + + int boundflag = BM_ELEM_CD_GET_INT(v, pbvh->cd_boundary_flag); + + if (boundflag & SCULPTVERT_ALL_BOUNDARY) { + continue; + } + + BMIter iter; + BMLoop *l; + BMLoop *ls[4]; + BMVert *vs[4]; + + l = v->e->l; + + if (!l) { + continue; + } + + if (l->v != v) { + l = l->next; + } + + bool bad = false; + int ls_i = 0; + + /* Don't dissolve verts if attached to long edges, to avoid + * preventing subdivision convergence. + */ + BMEdge *e = v->e; + do { + /* Double check edge boundary flags, in addition to the vertex boundary flag test above. */ + pbvh_check_edge_boundary_bmesh(pbvh, e); + if (BM_ELEM_CD_GET_INT(e, ectx->pbvh->cd_edge_boundary) & SCULPTVERT_ALL_BOUNDARY) { + bad = true; + break; + } + + float len = calc_weighted_length(ectx, e->v1, e->v2, SPLIT); + + if (sqrtf(len) > ectx->limit_len_max * 1.5f) { + bad = true; + break; + } + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + if (bad) { + continue; + } + + for (int j = 0; j < val; j++) { + ls[ls_i++] = l->v == v ? l->next : l; + + if (l->v == v) { + dyntopo_add_flag(pbvh, l->next->v, updateflag); + pbvh_boundary_update_bmesh(pbvh, l->next->v); + } + else { + dyntopo_add_flag(pbvh, l->v, updateflag); + pbvh_boundary_update_bmesh(pbvh, l->v); + } + + pbvh_boundary_update_bmesh(pbvh, l->e); + pbvh_boundary_update_bmesh(pbvh, l->next->e); + pbvh_boundary_update_bmesh(pbvh, l->prev->e); + + l = l->prev->radial_next; + + if (l->v != v) { + l = l->next; + } + + /* Ignore non-manifold edges along with ones flagged as sharp. */ + if (l->radial_next == l || l->radial_next->radial_next != l || + !(l->e->head.hflag & BM_ELEM_SMOOTH)) + { + bad = true; + break; + } + + if (l->radial_next != l && l->radial_next->v == l->v) { + bad = true; /* Bad normals. */ + break; + } + + for (int k = 0; k < j; k++) { + if (ls[k]->v == ls[j]->v) { + if (ls[j]->next->v != v) { + ls[j] = ls[j]->next; + } + else { + bad = true; + break; + } + } + + /* Check for non-manifold edges. */ + if (ls[k] != ls[k]->radial_next->radial_next) { + bad = true; + break; + } + + if (ls[k]->f == ls[j]->f) { + bad = true; + break; + } + } + } + + if (bad) { + continue; + } + + int ni = BM_ELEM_CD_GET_INT(v, pbvh->cd_vert_node_offset); + + if (ni < 0) { + continue; + } + + pbvh_bmesh_vert_remove(pbvh, v); + + BMFace *f; + BM_ITER_ELEM (f, &iter, v, BM_FACES_OF_VERT) { + int ni2 = BM_ELEM_CD_GET_INT(f, pbvh->cd_face_node_offset); + + if (ni2 != DYNTOPO_NODE_NONE) { + pbvh_bmesh_face_remove(pbvh, f, true, true, true); + } + else { + BM_log_face_removed(pbvh->header.bm, pbvh->bm_log, f); + } + } + + modified = true; + + if (!v->e) { + printf("mesh error!\n"); + continue; + } + + validate_vert(pbvh, v, CHECK_VERT_ALL); + + l = v->e->l; + + if (val == 4) { + /* Check which quad diagonal to use to split quad; + * try to preserve hard edges. + */ + + float n1[3], n2[3], th1, th2; + normal_tri_v3(n1, ls[0]->v->co, ls[1]->v->co, ls[2]->v->co); + normal_tri_v3(n2, ls[0]->v->co, ls[2]->v->co, ls[3]->v->co); + + th1 = dot_v3v3(n1, n2); + + normal_tri_v3(n1, ls[1]->v->co, ls[2]->v->co, ls[3]->v->co); + normal_tri_v3(n2, ls[1]->v->co, ls[3]->v->co, ls[0]->v->co); + + th2 = dot_v3v3(n1, n2); + + if (th1 > th2) { + BMLoop *ls2[4] = {ls[0], ls[1], ls[2], ls[3]}; + + for (int j = 0; j < 4; j++) { + ls[j] = ls2[(j + 1) % 4]; + } + } + + if (!BM_edge_exists(ls[0]->v, ls[2]->v)) { + BMEdge *e_diag = BM_edge_create( + pbvh->header.bm, ls[0]->v, ls[2]->v, nullptr, BM_CREATE_NOP); + BM_idmap_check_assign(pbvh->bm_idmap, e_diag); + BM_log_edge_added(pbvh->header.bm, pbvh->bm_log, e_diag); + } + } + + vs[0] = ls[0]->v; + vs[1] = ls[1]->v; + vs[2] = ls[2]->v; + + validate_vert(pbvh, v, CHECK_VERT_ALL); + + pbvh_boundary_update_bmesh(pbvh, vs[0]); + pbvh_boundary_update_bmesh(pbvh, vs[1]); + pbvh_boundary_update_bmesh(pbvh, vs[2]); + + dyntopo_add_flag(pbvh, vs[0], updateflag); + dyntopo_add_flag(pbvh, vs[1], updateflag); + dyntopo_add_flag(pbvh, vs[2], updateflag); + + BMFace *f1 = nullptr; + bool ok1 = vs[0] != vs[1] && vs[1] != vs[2] && vs[0] != vs[2]; + ok1 = ok1 && !BM_face_exists(vs, 3); + + if (ok1) { + f1 = pbvh_bmesh_face_create(pbvh, n, vs, nullptr, l->f, true, false); + BM_idmap_check_assign(pbvh->bm_idmap, f1); + + normal_tri_v3( + f1->no, f1->l_first->v->co, f1->l_first->next->v->co, f1->l_first->prev->v->co); + + validate_face(pbvh, f1, CHECK_FACE_NONE); + } + + BMFace *f2 = nullptr; + bool ok2 = false; + + if (val == 4) { + vs[0] = ls[0]->v; + vs[1] = ls[2]->v; + vs[2] = ls[3]->v; + + ok2 = vs[0] != vs[1] && vs[1] != vs[2] && vs[2] != vs[0]; + ok2 = ok2 && !BM_face_exists(vs, 3); + } + + if (ok2) { + pbvh_boundary_update_bmesh(pbvh, vs[0]); + pbvh_boundary_update_bmesh(pbvh, vs[1]); + pbvh_boundary_update_bmesh(pbvh, vs[2]); + + dyntopo_add_flag(pbvh, vs[0], updateflag); + dyntopo_add_flag(pbvh, vs[1], updateflag); + dyntopo_add_flag(pbvh, vs[2], updateflag); + + BMFace *example = nullptr; + if (v->e && v->e->l) { + example = v->e->l->f; + } + + f2 = pbvh_bmesh_face_create(pbvh, n, vs, nullptr, example, true, false); + BM_idmap_check_assign(pbvh->bm_idmap, f2); + + bmesh_swap_data_simple(&pbvh->header.bm->ldata, + &f2->l_first->prev->head.data, + &ls[3]->head.data, + pbvh->bm_idmap->cd_id_off[BM_LOOP]); + CustomData_bmesh_copy_data(&pbvh->header.bm->ldata, + &pbvh->header.bm->ldata, + ls[0]->head.data, + &f2->l_first->head.data); + CustomData_bmesh_copy_data(&pbvh->header.bm->ldata, + &pbvh->header.bm->ldata, + ls[2]->head.data, + &f2->l_first->next->head.data); + + normal_tri_v3( + f2->no, f2->l_first->v->co, f2->l_first->next->v->co, f2->l_first->prev->v->co); + BM_log_face_added(pbvh->header.bm, pbvh->bm_log, f2); + + validate_face(pbvh, f2, CHECK_FACE_MANIFOLD); + } + + if (f1) { + bmesh_swap_data_simple(&pbvh->header.bm->ldata, + &f1->l_first->head.data, + &ls[0]->head.data, + pbvh->bm_idmap->cd_id_off[BM_LOOP]); + bmesh_swap_data_simple(&pbvh->header.bm->ldata, + &f1->l_first->next->head.data, + &ls[1]->head.data, + pbvh->bm_idmap->cd_id_off[BM_LOOP]); + bmesh_swap_data_simple(&pbvh->header.bm->ldata, + &f1->l_first->prev->head.data, + &ls[2]->head.data, + pbvh->bm_idmap->cd_id_off[BM_LOOP]); + + BM_log_face_added(pbvh->header.bm, pbvh->bm_log, f1); + validate_face(pbvh, f1, CHECK_FACE_MANIFOLD); + } + + validate_vert(pbvh, v, CHECK_VERT_ALL); + pbvh_kill_vert(pbvh, v, true, true); + + if (f1 && !bm_elem_is_free((BMElem *)f1, BM_FACE)) { + check_face_is_manifold(pbvh, f1); + } + + if (f2 && !bm_elem_is_free((BMElem *)f2, BM_FACE)) { + check_face_is_manifold(pbvh, f2); + } + } + + if (modified) { + pbvh->header.bm->elem_index_dirty |= BM_VERT | BM_FACE | BM_EDGE; + pbvh->header.bm->elem_table_dirty |= BM_VERT | BM_FACE | BM_EDGE; + } + + return modified; +} + +static bool do_cleanup_3_4(EdgeQueueContext *eq_ctx, PBVH *pbvh) +{ + bool modified = false; + + eq_ctx->used_verts.clear(); + + for (const PBVHNode &node : pbvh->nodes) { + if (!(node.flag & PBVH_Leaf) || !(node.flag & PBVH_UpdateTopology)) { + continue; + } + + for (BMVert *v : *node.bm_unique_verts) { + if (dyntopo_test_flag(pbvh, v, SCULPTFLAG_NEED_VALENCE)) { + BKE_pbvh_bmesh_update_valence(pbvh, {(intptr_t)v}); + } + + if (BM_ELEM_CD_GET_INT(v, pbvh->cd_valence) > 4) { + continue; + } + + bool ok = eq_ctx->brush_tester->vert_in_range(v); + + if (!ok && v->e) { + /* Check if any surrounding vertex is in range. */ + BMEdge *e = v->e; + do { + BMVert *v2 = BM_edge_other_vert(e, v); + + if (eq_ctx->brush_tester->vert_in_range(v2)) { + ok = true; + break; + } + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + } + + if (ok) { + eq_ctx->insert_val34_vert(v); + } + } + } + + BM_log_entry_add_delta_set(pbvh->header.bm, pbvh->bm_log); + + pbvh_bmesh_check_nodes(pbvh); + + modified |= cleanup_valence_3_4(eq_ctx, pbvh); + pbvh_bmesh_check_nodes(pbvh); + + return modified; +} + +float mask_cb_nop(PBVHVertRef /*vertex*/, void * /*userdata*/) +{ + return 1.0f; +} + +EdgeQueueContext::EdgeQueueContext(BrushTester *brush_tester_, + Object *ob, + PBVH *pbvh_, + PBVHTopologyUpdateMode mode_, + bool use_frontface_, + float3 view_normal_, + bool updatePBVH_, + DyntopoMaskCB mask_cb_, + void *mask_cb_data_) +{ + ss = ob->sculpt; + + pbvh = pbvh_; + brush_tester = brush_tester_; + use_view_normal = use_frontface_; + view_normal = view_normal_; + + pool = nullptr; + bm = pbvh->header.bm; + mask_cb = mask_cb_; + mask_cb_data = mask_cb_data_; + view_normal = view_normal_; + + ignore_loop_data = !bm->ldata.totlayer; + + updatePBVH = updatePBVH_; + cd_vert_mask_offset = pbvh->cd_vert_mask_offset; + cd_vert_node_offset = pbvh->cd_vert_node_offset; + cd_face_node_offset = pbvh->cd_face_node_offset; + local_mode = false; + mode = mode_; + + surface_relax = true; + distort_correction_mode = ss->distort_correction_mode; + + limit_len_min = pbvh->bm_min_edge_len; + limit_len_max = pbvh->bm_max_edge_len; + limit_len_min_sqr = limit_len_min * limit_len_min; + limit_len_max_sqr = limit_len_max * limit_len_max; + limit_mid = limit_len_max * 0.5f + limit_len_min * 0.5f; + + surface_smooth_fac = DYNTOPO_SAFE_SMOOTH_FAC; + +#ifdef DYNTOPO_REPORT + report(); +#endif + +#if DYNTOPO_DISABLE_FLAG & DYNTOPO_DISABLE_COLLAPSE + mode &= ~PBVH_Collapse; +#endif +#if DYNTOPO_DISABLE_FLAG & DYNTOPO_DISABLE_SPLIT_EDGES + mode &= ~PBVH_Subdivide; +#endif + + if (mode & (PBVH_Subdivide | PBVH_Collapse)) { + unified_edge_queue_create(this, pbvh, mode & (PBVH_LocalSubdivide | PBVH_LocalCollapse)); + } +} + +void EdgeQueueContext::start() +{ + /* Preemptively log UVs. */ + if (!ignore_loop_data) { + for (int i : IndexRange(pbvh->nodes.size())) { + PBVHNode *node = &pbvh->nodes[i]; + + if ((node->flag & PBVH_Leaf) && (node->flag & PBVH_UpdateTopology)) { + for (BMFace *f : *node->bm_faces) { + BM_log_face_if_modified(bm, pbvh->bm_log, f); + } + } + } + } +} + +ATTR_NO_OPT bool EdgeQueueContext::done() +{ +#ifndef DYNTOPO_USE_SEP_HEAPS + if (edge_heap.empty() || + (edge_heap.min_weight() > limit_len_min_sqr && edge_heap.max_weight() < limit_len_max_sqr)) + { + return true; + } +#else + if (min_heap.empty() && max_heap.empty()) { + return true; + } + + if (!min_heap.empty() && !max_heap.empty() && min_heap.top_weight() > limit_len_min_sqr && + max_heap.top_weight() < limit_len_max_sqr) + { + return true; + } +#endif + + return (mode & (PBVH_Collapse | PBVH_Subdivide | PBVH_LocalCollapse)) == 0; +} + +bool EdgeQueueContext::cleanup_valence_34() +{ + return do_cleanup_3_4(this, pbvh); +} + +void EdgeQueueContext::finish() +{ +#ifdef DYNTOPO_USE_SEP_HEAPS + while (!min_heap.empty()) { + min_heap.pop()->head.hflag &= ~EDGE_QUEUE_FLAG; + } + while (!max_heap.empty()) { + max_heap.pop()->head.hflag &= ~EDGE_QUEUE_FLAG; + } +#else + for (BMEdge *e : edge_heap.values()) { + if (!BM_elem_is_free(reinterpret_cast(e), BM_EDGE)) { + e->head.hflag &= ~EDGE_QUEUE_FLAG; + } + } +#endif + + if (mode & PBVH_Cleanup) { + modified |= do_cleanup_3_4(this, pbvh); + + VALIDATE_LOG(pbvh->bm_log); + } + + if (modified) { + /* Avoid potential infinite loops. */ + const int totnode = pbvh->nodes.size(); + + for (int i = 0; i < totnode; i++) { + PBVHNode *node = &pbvh->nodes[i]; + + if ((node->flag & PBVH_Leaf) && (node->flag & PBVH_UpdateTopology) && + !(node->flag & PBVH_FullyHidden)) + { + + /* do not clear PBVH_UpdateTopology here in case split messes with it */ + + /* Recursively split nodes that have gotten too many + * elements */ + if (updatePBVH) { // && !(G.debug_value & 1024)) { + // pbvh_bmesh_node_limit_ensure(pbvh, i); + } + } + } + } + + /* clear PBVH_UpdateTopology flags */ + for (int i = 0; i < pbvh->nodes.size(); i++) { + PBVHNode *node = &pbvh->nodes[i]; + + if (!(node->flag & PBVH_Leaf)) { + continue; + } + + node->flag &= ~PBVH_UpdateTopology; + } + +#ifdef USE_VERIFY + pbvh_bmesh_verify(pbvh); +#endif + + /* Ensure triangulations are all up to date. */ + for (int i = 0; i < pbvh->nodes.size(); i++) { + PBVHNode *node = &pbvh->nodes[i]; + + if (node->flag & PBVH_Leaf) { + pbvh_bmesh_check_other_verts(node); + BKE_pbvh_bmesh_check_tris(pbvh, node); + } + } + + if (modified) { + blender::bke::pbvh::update_bounds(*pbvh, PBVH_UpdateBB | PBVH_UpdateOriginalBB); + } + + /* Push a subentry. */ + BM_log_entry_add_delta_set(pbvh->header.bm, pbvh->bm_log); +} + +template +BMEdge *EdgeQueueContext::pop_invalid_edges(EdgeHeapT & /*heap*/, + BMEdge *in_e, + float &w, + bool is_max) +{ + if (!in_e) { + return nullptr; + } + +#ifdef DYNTOPO_USE_SEP_HEAPS + BMEdge *e = in_e; + WeightMode weightmode = is_max ? SPLIT : COLLAPSE; + + while (!heap.empty() && e && + (bm_elem_is_free((BMElem *)e, BM_EDGE) || + fabs(calc_weighted_length(this, e->v1, e->v2, weightmode) - w) > w * 0.1f)) + { + heap.pop(); + + if (heap.empty()) { + return nullptr; + } + + /* The edge was freed. */ + if (bm_elem_is_free((BMElem *)e, BM_EDGE)) { + e = heap.pop(&w); + continue; + } + + /* The weight was wrong. */ + e->head.hflag |= EDGE_QUEUE_FLAG; + heap.insert(e, calc_weighted_length(this, e->v1, e->v2, weightmode)); + + e = heap.top(&w); + } + +#else + BMEdge *e = in_e; + WeightMode weightmode = is_max ? SPLIT : COLLAPSE; + + while (!edge_heap.empty() && e && + (bm_elem_is_free((BMElem *)e, BM_EDGE) || + fabs(calc_weighted_length(this, e->v1, e->v2, weightmode) - w) > w * 0.1f)) + { + if (is_max) { + edge_heap.pop_max(); + } + else { + edge_heap.pop_min(); + } + + if (edge_heap.empty()) { + return nullptr; + } + + /* The edge was freed. */ + if (bm_elem_is_free((BMElem *)e, BM_EDGE)) { + e = is_max ? edge_heap.pop_max(&w) : edge_heap.pop_min(&w); + continue; + } + + /* The weight was wrong. */ + e->head.hflag |= EDGE_QUEUE_FLAG; + edge_heap.insert(calc_weighted_length(this, e->v1, e->v2, weightmode), e); + + if (is_max) { + e = edge_heap.peek_max(&w); + } + else { + e = edge_heap.peek_min(&w); + } + } + +#endif + + return BM_elem_is_free(reinterpret_cast(e), BM_EDGE) ? nullptr : e; +} + +void EdgeQueueContext::stochastic_smooth(BMVert *v) +{ + if (!surface_relax) { + return; + } + + if (rand.get_float() > 0.9) { + surface_smooth_v_safe(ss, + pbvh, + v, + surface_smooth_fac * + mask_cb({reinterpret_cast(v)}, mask_cb_data), + distort_correction_mode); + } +} + +/* Collapse and subdivide. + * + * Note: we enforce subdivide-only or collapse-only + * in unified_edge_queue_create, for the solver to + * converge properly edge splitting needs to be + * able to collapse degenerate edges. + */ +void EdgeQueueContext::step_intern() +{ + + float min_w, max_w; + +#ifdef DYNTOPO_USE_SEP_HEAPS + BMEdge *min_e = !min_heap.empty() ? min_heap.top(&min_w) : nullptr; + BMEdge *max_e = !max_heap.empty() ? max_heap.top(&max_w) : nullptr; + + min_e = pop_invalid_edges(min_heap, min_e, min_w, false); + max_e = pop_invalid_edges(max_heap, max_e, max_w, false); +#else + BMEdge *min_e = edge_heap.peek_min(&min_w); + min_e = pop_invalid_edges(edge_heap, min_e, min_w, false); + if (edge_heap.empty()) { + return; + } + + BMEdge *max_e = edge_heap.peek_max(&max_w); + max_e = pop_invalid_edges(edge_heap, max_e, max_w, true); + if (edge_heap.empty()) { + return; + } +#endif + + if (min_w > limit_len_min_sqr) { + min_e = nullptr; + } + if (max_w < limit_len_max_sqr) { + max_e = nullptr; + } + + if (!min_e && !max_e) { + return; + } + + BMEdge *e = nullptr; + + PBVHTopologyUpdateMode op; + if (min_e && max_e && (min_e == max_e || fabs(min_w - max_w) < 0.0001f)) { + op = (count % 2) ? PBVH_Subdivide : PBVH_Collapse; + if (op == PBVH_Subdivide) { + e = max_e; + } + else { + e = min_e; + } + } + else if (!min_e) { + e = max_e; + op = PBVH_Subdivide; + } + else if (!max_e) { + e = min_e; + op = PBVH_Collapse; + } + else { + float l1 = sqrtf(calc_weighted_length(this, min_e->v1, min_e->v2, COLLAPSE)); + float l2 = sqrtf(calc_weighted_length(this, max_e->v1, max_e->v2, SPLIT)); + + if ((limit_len_min - l1) * 2.0 > (l2 - limit_len_max)) { + op = PBVH_Collapse; + e = min_e; + } + else { + op = PBVH_Subdivide; + e = max_e; + } + } + + if (!e) { + return; + } + + modified = true; + e->head.hflag &= ~EDGE_QUEUE_FLAG; + + stochastic_smooth(e->v1); + stochastic_smooth(e->v2); + + if (op == PBVH_Collapse) { +#ifdef DYNTOPO_USE_SEP_HEAPS + min_heap.pop(); +#else + edge_heap.pop_min(); +#endif + collapse_edge(pbvh, e, e->v1, e->v2); + } + else if (op == PBVH_Subdivide) { +#ifdef DYNTOPO_USE_SEP_HEAPS + max_heap.pop(); +#else + edge_heap.pop_max(); +#endif + split_edge(e); + } + + count++; +} + +void EdgeQueueContext::step() +{ + if (done()) { + return; + } + + /* TODO: this has something to do with detail floodfill op, review it. */ + if (count % 100 == 0) { + flushed_ = true; + } + + step_intern(); +} + +void EdgeQueueContext::report() +{ + BMesh *bm = pbvh->header.bm; + + int vmem = (int)((size_t)bm->totvert * (sizeof(BMVert) + bm->vdata.totsize)); + int emem = (int)((size_t)bm->totedge * (sizeof(BMEdge) + bm->edata.totsize)); + int lmem = (int)((size_t)bm->totloop * (sizeof(BMLoop) + bm->ldata.totsize)); + int fmem = (int)((size_t)bm->totface * (sizeof(BMFace) + bm->pdata.totsize)); + + double fvmem = (double)vmem / 1024.0 / 1024.0; + double femem = (double)emem / 1024.0 / 1024.0; + double flmem = (double)lmem / 1024.0 / 1024.0; + double ffmem = (double)fmem / 1024.0 / 1024.0; + + printf("totmem: %.2fmb\n", fvmem + femem + flmem + ffmem); + printf("v: %.2f e: %.2f l: %.2f f: %.2f\n", fvmem, femem, flmem, ffmem); + + printf("custom attributes only:\n"); + vmem = (int)((size_t)bm->totvert * (bm->vdata.totsize)); + emem = (int)((size_t)bm->totedge * (bm->edata.totsize)); + lmem = (int)((size_t)bm->totloop * (bm->ldata.totsize)); + fmem = (int)((size_t)bm->totface * (bm->pdata.totsize)); + + fvmem = (double)vmem / 1024.0 / 1024.0; + femem = (double)emem / 1024.0 / 1024.0; + flmem = (double)lmem / 1024.0 / 1024.0; + ffmem = (double)fmem / 1024.0 / 1024.0; + + printf("v: %.2f e: %.2f l: %.2f f: %.2f\n", fvmem, femem, flmem, ffmem); +} + +bool remesh_topology(BrushTester *brush_tester, + Object *ob, + PBVH *pbvh, + PBVHTopologyUpdateMode mode, + bool use_frontface, + float3 view_normal, + bool updatePBVH, + DyntopoMaskCB mask_cb, + void *mask_cb_data, + float quality) +{ + BM_log_entry_check_customdata(pbvh->header.bm, pbvh->bm_log); + + EdgeQueueContext eq_ctx( + brush_tester, ob, pbvh, mode, use_frontface, view_normal, updatePBVH, mask_cb, mask_cb_data); + eq_ctx.start(); + + /* Apply a time limit to avoid excessive hangs on pathological topology. */ + + using Clock = std::chrono::high_resolution_clock; + + quality *= quality; + int time_limit = int(8.0f * (1.0f - quality) + 550.0f * quality); + + auto time = Clock::now(); + Clock::duration limit = std::chrono::duration_cast( + std::chrono::milliseconds(time_limit)); + + while (!eq_ctx.done()) { + eq_ctx.step(); + + if ((Clock::now() - time) > limit) { + break; + } + } + + eq_ctx.finish(); + +#if 0 + printf("time: %dms\n", + int(std::chrono::duration_cast(Clock::now() - + time).count())); +#endif + return eq_ctx.modified; +} + +void detail_size_set(PBVH *pbvh, float detail_size, float detail_range) +{ + detail_range = max_ff(detail_range, 0.1f); + + detail_size /= detail_range; + + pbvh->bm_detail_range = detail_range; + pbvh->bm_max_edge_len = detail_size; + pbvh->bm_min_edge_len = detail_size * detail_range; +} +} // namespace blender::bke::dyntopo + +void BKE_pbvh_bmesh_remove_face(PBVH *pbvh, BMFace *f, bool log_face) +{ + blender::bke::dyntopo::pbvh_bmesh_face_remove(pbvh, f, log_face, true, true); +} + +void BKE_pbvh_bmesh_remove_edge(PBVH *pbvh, BMEdge *e, bool log_edge) +{ + if (log_edge) { + BM_log_edge_removed(pbvh->header.bm, pbvh->bm_log, e); + } +} + +void BKE_pbvh_bmesh_remove_vertex(PBVH *pbvh, BMVert *v, bool log_vert) +{ + blender::bke::dyntopo::pbvh_bmesh_vert_remove(pbvh, v); + + if (log_vert) { + BM_log_vert_removed(pbvh->header.bm, pbvh->bm_log, v); + } +} + +void BKE_pbvh_bmesh_add_face(PBVH *pbvh, struct BMFace *f, bool log_face, bool force_tree_walk) +{ + int ni = DYNTOPO_NODE_NONE; + + if (force_tree_walk) { + bke_pbvh_insert_face(pbvh, f); + + if (log_face) { + BM_log_face_added(pbvh->header.bm, pbvh->bm_log, f); + } + + return; + } + + /* Look for node in srounding geometry. */ + BMLoop *l = f->l_first; + do { + int ni2 = BM_ELEM_CD_GET_INT(l->radial_next->f, pbvh->cd_face_node_offset); + + if (ni2 >= 0 && (ni2 >= pbvh->nodes.size() || !(pbvh->nodes[ni2].flag & PBVH_Leaf))) { + printf("%s: error: ni: %d totnode: %d\n", __func__, ni2, int(pbvh->nodes.size())); + l = l->next; + continue; + } + + if (ni2 >= 0 && (pbvh->nodes[ni2].flag & PBVH_Leaf)) { + ni = ni2; + break; + } + + l = l->next; + } while (l != f->l_first); + + if (ni < 0) { + bke_pbvh_insert_face(pbvh, f); + } + else { + BM_ELEM_CD_SET_INT(f, pbvh->cd_face_node_offset, ni); + bke_pbvh_insert_face_finalize(pbvh, f, ni); + } + + if (log_face) { + BM_log_face_added(pbvh->header.bm, pbvh->bm_log, f); + } +} + +namespace blender::bke::dyntopo { +void EdgeQueueContext::split_edge(BMEdge *e) +{ + BMesh *bm = pbvh->header.bm; + BMEdge *newe; + BMFace *newf = nullptr; + + PBVH_CHECK_NAN(e->v1->co); + PBVH_CHECK_NAN(e->v2->co); + + if (!e->l) { + return; + } + + check_vert_fan_are_tris(pbvh, e->v1); + check_vert_fan_are_tris(pbvh, e->v2); + + Vector fs; + + BMLoop *l = e->l; + do { + if (BM_ELEM_CD_GET_INT(l->f, pbvh->cd_face_node_offset) != DYNTOPO_NODE_NONE) { + pbvh_bmesh_face_remove(pbvh, l->f, true, true, true); + BM_idmap_release(pbvh->bm_idmap, l->f, true); + fs.append(l->f); + } + + BMLoop *l2 = l->f->l_first; + + do { + pbvh_boundary_update_bmesh(pbvh, l2->v); + pbvh_boundary_update_bmesh(pbvh, l2->e); + + dyntopo_add_flag(pbvh, l2->v, SCULPTFLAG_NEED_VALENCE); + } while ((l2 = l2->next) != l->f->l_first); + } while ((l = l->radial_next) != e->l); + + BM_log_edge_removed(bm, pbvh->bm_log, e); + BM_idmap_release(pbvh->bm_idmap, e, true); + + StrokeID stroke_id1 = blender::bke::paint::vertex_attr_get( + {reinterpret_cast(e->v1)}, ss->attrs.stroke_id); + StrokeID stroke_id2 = blender::bke::paint::vertex_attr_get( + {reinterpret_cast(e->v2)}, ss->attrs.stroke_id); + + dyntopo_add_flag(pbvh, e->v1, SCULPTFLAG_NEED_VALENCE); + dyntopo_add_flag(pbvh, e->v2, SCULPTFLAG_NEED_VALENCE); + + pbvh_boundary_update_bmesh(pbvh, e->v1); + pbvh_boundary_update_bmesh(pbvh, e->v2); + pbvh_boundary_update_bmesh(pbvh, e); + + bool uv_boundary = BM_ELEM_CD_GET_INT(e, pbvh->cd_edge_boundary) & SCULPT_BOUNDARY_UV; + bool sharp_angle_boundary = BM_ELEM_CD_GET_INT(e, pbvh->cd_edge_boundary) & + SCULPT_BOUNDARY_SHARP_ANGLE; + + BMVert *newv = BM_edge_split(bm, e, e->v1, &newe, 0.5f); + + PBVH_CHECK_NAN(newv->co); + + /* Remove edge-in-minmax-heap tag. */ + e->head.hflag &= ~EDGE_QUEUE_FLAG; + newe->head.hflag &= ~EDGE_QUEUE_FLAG; + + BM_ELEM_CD_SET_INT(newe, pbvh->cd_edge_boundary, BM_ELEM_CD_GET_INT(e, pbvh->cd_edge_boundary)); + + /* Do not allow vertex uv boundary flags to propagate across non-boundary edges. */ + if (!uv_boundary) { + *BM_ELEM_CD_PTR(newv, + pbvh->cd_boundary_flag) &= ~(SCULPT_BOUNDARY_UV | SCULPT_CORNER_UV); + } + else { + *BM_ELEM_CD_PTR(newv, pbvh->cd_boundary_flag) |= SCULPT_BOUNDARY_UV; + *BM_ELEM_CD_PTR(newv, pbvh->cd_boundary_flag) &= ~SCULPT_CORNER_UV; + } + + /* Do not allow sharp angle boundary flags to propagate across non-boundary edges. */ + if (!sharp_angle_boundary) { + *BM_ELEM_CD_PTR(newv, pbvh->cd_boundary_flag) &= ~(SCULPT_BOUNDARY_SHARP_ANGLE | + SCULPT_CORNER_SHARP_ANGLE); + } + else { + *BM_ELEM_CD_PTR(newv, pbvh->cd_boundary_flag) |= SCULPT_BOUNDARY_SHARP_ANGLE; + *BM_ELEM_CD_PTR(newv, pbvh->cd_boundary_flag) &= ~SCULPT_CORNER_SHARP_ANGLE; + } + + /* Propagate current stroke id. */ + StrokeID stroke_id; + + if (stroke_id1.id < stroke_id2.id) { + std::swap(stroke_id1, stroke_id2); + } + + stroke_id.id = stroke_id1.id; + if (stroke_id2.id < stroke_id1.id) { + stroke_id.userflag = stroke_id1.userflag; + } + else { + stroke_id.userflag = stroke_id1.userflag & stroke_id2.userflag; + } + + *BM_ELEM_CD_PTR(newv, ss->attrs.stroke_id->bmesh_cd_offset) = stroke_id; + + BM_ELEM_CD_SET_INT(newv, pbvh->cd_vert_node_offset, DYNTOPO_NODE_NONE); + + BM_idmap_check_assign(pbvh->bm_idmap, newv); + BM_log_vert_added(bm, pbvh->bm_log, newv); + + BM_idmap_check_assign(pbvh->bm_idmap, e); + BM_log_edge_added(bm, pbvh->bm_log, e); + + BM_idmap_check_assign(pbvh->bm_idmap, newe); + BM_log_edge_added(bm, pbvh->bm_log, newe); + + dyntopo_add_flag(pbvh, newv, SCULPTFLAG_NEED_VALENCE | SCULPTFLAG_NEED_TRIANGULATE); + + pbvh_boundary_update_bmesh(pbvh, newv); + pbvh_boundary_update_bmesh(pbvh, newe); + + for (BMFace *f : fs) { + BM_ELEM_CD_SET_INT(f, pbvh->cd_face_node_offset, DYNTOPO_NODE_NONE); + } + + for (BMFace *f : fs) { + BMLoop *l = f->l_first; + do { + if (l->v == newv) { + break; + } + } while ((l = l->next) != f->l_first); + + BMEdge *exist_e = BM_edge_exists(l->v, l->next->next->v); + BMLoop *newl; + newf = BM_face_split(bm, f, l, l->next->next, &newl, nullptr, true); + + if (newf) { + BM_ELEM_CD_SET_INT(newf, pbvh->cd_face_node_offset, DYNTOPO_NODE_NONE); + } + + dyntopo_add_flag( + pbvh, l->next->next->v, SCULPTFLAG_NEED_VALENCE | SCULPTFLAG_NEED_TRIANGULATE); + pbvh_boundary_update_bmesh(pbvh, l->next->next->v); + + pbvh_boundary_update_bmesh(pbvh, l->next->e); + pbvh_boundary_update_bmesh(pbvh, l->next->next->e); + pbvh_boundary_update_bmesh(pbvh, l->next->next->next->e); + + BM_ELEM_CD_SET_INT(f, pbvh->cd_face_node_offset, DYNTOPO_NODE_NONE); + BM_idmap_check_assign(pbvh->bm_idmap, f); + BKE_pbvh_bmesh_add_face(pbvh, f, true, false); + + if (!newf || newf == f) { + continue; + } + + BM_ELEM_CD_SET_INT(newf, pbvh->cd_face_node_offset, DYNTOPO_NODE_NONE); + BM_idmap_check_assign(pbvh->bm_idmap, f); + BKE_pbvh_bmesh_add_face(pbvh, newf, true, false); + + if (!exist_e) { + BM_idmap_check_assign(pbvh->bm_idmap, newl->e); + BM_log_edge_added(bm, pbvh->bm_log, newl->e); + } + + newl->e->head.hflag &= ~EDGE_QUEUE_FLAG; + + copy_v3_v3(newf->no, f->no); + + BMVert *vs[3] = {newl->v, newl->next->v, newl->next->next->v}; + if (brush_tester->tri_in_range(vs, newl->f->no)) { + float w = 0.0f; + WeightMode mode = edge_queue_test(this, pbvh, newl->e, &w); + + if (mode == SPLIT) { + add_split_edge_recursive(this, newl, w, limit_len_max, 0); + } + else if (mode == COLLAPSE) { + insert_edge(newl->e, w, mode); + } + } + else { + float w = 0.0f; + WeightMode mode = edge_queue_test(this, pbvh, newl->e, &w); + + if (mode == COLLAPSE) { + insert_edge(newl->e, w, mode); + } + } + } + + pbvh_bmesh_check_nodes(pbvh); + + check_for_fins(pbvh, newv); +} +} // namespace blender::bke::dyntopo + +#include + +namespace myinterp { + +int ignore_check = 0; +struct ignore { + int f = 0; + + ignore() + { + f = ignore_check++; + } + ~ignore() + { + f = ignore_check--; + } + ignore(const ignore &b) = delete; +}; + +template constexpr T get_epsilon() +{ + if constexpr (std::is_same_v) { + return FLT_EPSILON; + } + else if constexpr (std::is_same_v) { + return DBL_EPSILON; + } + else if constexpr (std::is_same_v) { + return DBL_EPSILON; + } + else { + return T::EPSILON(); + } +} + +#define IS_POINT_IX (1 << 0) +#define IS_SEGMENT_IX (1 << 1) + +#define DIR_V3_SET(d_len, va, vb) \ + { \ + sub_v3_v3v3((d_len)->dir, va, vb); \ + (d_len)->len = len_v3((d_len)->dir); \ + } \ + (void)0 + +#define DIR_V2_SET(d_len, va, vb) \ + { \ + sub_v2_v2v2((d_len)->dir, va, vb); \ + (d_len)->len = len_v2((d_len)->dir); \ + } \ + (void)0 + +template struct Float3_Len { + T dir[3], len; +}; + +template struct Double2_Len { + T2 dir[2], len; +}; + +template +void sub_v2_v2v2(T1 r[3], const T2 a[3], const T2 b[3]) +{ + r[0] = T1(a[0] - b[0]); + r[1] = T1(a[1] - b[1]); +} + +template +void sub_v3_v3v3(T1 r[3], const T2 a[3], const T2 b[3]) +{ + r[0] = T1(a[0] - b[0]); + r[1] = T1(a[1] - b[1]); + r[2] = T1(a[2] - b[2]); +} + +template T cross_v2v2(const T *a, const T *b) +{ + return a[0] * b[1] - a[1] * b[0]; +} + +template void cross_v3_v3v3(T r[3], const T a[3], const T b[3]) +{ + BLI_assert(r != a && r != b); + r[0] = a[1] * b[2] - a[2] * b[1]; + r[1] = a[2] * b[0] - a[0] * b[2]; + r[2] = a[0] * b[1] - a[1] * b[0]; +} + +#define VEC_TEMPLATE(n) \ + template> + +VEC_TEMPLATE(2) Float len_squared_v2v2(const Vec a, const Vec b) +{ + Float dx = a[0] - b[0]; + Float dy = a[1] - b[1]; + return dx * dx + dy * dy; +} + +VEC_TEMPLATE(2) Float dot_v2v2(const Vec &a, const Vec &b) +{ + return a[0] * b[0] + a[1] * b[1]; +} + +VEC_TEMPLATE(3) Float dot_v3v3(const Vec &a, const Vec &b) +{ + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} + +VEC_TEMPLATE(2) Float len_squared_v2(const Vec &a) +{ + return dot_v2v2(a, a); +} + +template T len_v2(const T *a) +{ + return std::sqrt(len_squared_v2(a)); +} + +VEC_TEMPLATE(2) Float len_v2v2(const Vec a, const Vec b) +{ + return std::sqrt(len_squared_v2v2(a, b)); +} + +template void sub_v2_v2v2(T *r, const T *a, const T *b) +{ + r[0] = a[0] - b[0]; + r[1] = a[1] - b[1]; +} + +template void mul_v2_fl(T *r, T f) +{ + r[0] *= f; + r[1] *= f; +} + +template void negate_v2(T *r) +{ + r[0] = -r[0]; + r[1] = -r[1]; +} + +VEC_TEMPLATE(2) Float normalize_v2(Vec r) +{ + Float len = dot_v2v2(r, r); + if (len >= get_epsilon()) { + r[0] /= len; + r[1] /= len; + } + + return len; +} + +template T normalize_v3(T *r) +{ + T len = dot_v3v3(r, r); + + if (len >= get_epsilon()) { + r[0] /= len; + r[1] /= len; + r[2] /= len; + } + + return len; +} + +/* Mean value weights - smooth interpolation weights for polygons with + * more than 3 vertices */ +template +T mean_value_half_tan_v3(const struct Float3_Len *d_curr, const struct Float3_Len *d_next) +{ + T cross[3]; + cross_v3_v3v3(cross, d_curr->dir, d_next->dir); + const T area = len_v3(cross); + /* Compare against zero since 'FLT_EPSILON' can be too large, see: #73348. */ + if (LIKELY(area != 0.0)) { + const T dot = dot_v3v3(d_curr->dir, d_next->dir); + const T len = d_curr->len * d_next->len; + const T result = (len - dot) / area; + if (std::isfinite(result)) { + return result; + } + } + return 0.0; +} + +/** + * Mean value weights - same as #mean_value_half_tan_v3 but for 2D vectors. + * + * \note When interpolating a 2D polygon, a point can be considered "outside" + * the polygon's bounds. Thus, when the point is very distant and the vectors + * have relatively close values, the precision problems are evident since they + * do not indicate a point "inside" the polygon. + * To resolve this, doubles are used. + */ +template +T2 mean_value_half_tan_v2_db(const struct Double2_Len *d_curr, + const struct Double2_Len *d_next) +{ + /* Different from the 3d version but still correct. */ + const T2 area = cross_v2v2(d_curr->dir, d_next->dir); + /* Compare against zero since 'FLT_EPSILON' can be too large, see: #73348. */ + if (LIKELY(area != 0.0)) { + const T2 dot = dot_v2v2(d_curr->dir, d_next->dir); + const T2 len = d_curr->len * d_next->len; + const T2 result = (len - dot) / area; + if (std::isfinite(result)) { + return result; + } + } + return 0.0; +} + +template +T line_point_factor_v2_ex( + const T p[2], const T l1[2], const T l2[2], const T epsilon, const T fallback) +{ + T h[2], u[2]; + T dot; + sub_v2_v2v2(u, l2, l1); + sub_v2_v2v2(h, p, l1); + + /* better check for zero */ + dot = len_squared_v2(u); + return (dot > epsilon) ? (dot_v2v2(u, h) / dot) : fallback; +} + +template T line_point_factor_v2(const T p[2], const T l1[2], const T l2[2]) +{ + return line_point_factor_v2_ex(p, l1, l2, 0.0, 0.0); +} +template +T dist_squared_to_line_segment_v2(const T *p, const T *v1, const T *v2) +{ + T dx1 = p[0] - v1[0]; + T dy1 = p[1] - v1[1]; + + T dx2 = v2[0] - v1[0]; + T dy2 = v2[1] - v1[1]; + + T len_sqr = len_squared_v2v2(v1, v2); + T len = std::sqrt(len_sqr); + + bool good; + { + ignore scope; + good = len > get_epsilon() * 32.0; + } + + if (good) { + dx2 /= len; + dy2 /= len; + } + else { + return len_squared_v2v2(p, v1); + } + + T fac = dx1 * dx2 + dy1 * dy2; + bool test; + + { + ignore scope; + test = fac <= get_epsilon() * 32.0; + } + + if (test) { + return len_squared_v2v2(p, v1); + } + else { + { + ignore scope; + test = fac >= len - get_epsilon() * 32.0; + } + if (test) { + return len_squared_v2v2(p, v2); + } + } + + return std::fabs(dx1 * dy2 - dy1 * dx2); +} + +template +void interp_weights_poly_v2(T *w, T v[][2], const int n, const T _co[2]) +{ + /* Before starting to calculate the weight, we need to figure out the floating point precision we + * can expect from the supplied data. */ + T max_value = 0.0; + T co[2] = {_co[0], _co[1]}; + + T min[2], max[2]; + min[0] = min[1] = T(1e17); + max[0] = max[1] = T(-1e17); + + for (int i = 0; i < n; i++) { + for (int j = 0; j < 2; j++) { + min[j] = std::min(min[j], v[i][j]); + max[j] = std::max(max[j], v[i][j]); + } + } + + max[0] -= min[0]; + max[1] -= min[1]; + bool test1; + { + ignore scope; + test1 = max[0] > get_epsilon() * 1000.0 && max[1] > get_epsilon() * 1000.0; + } + + if (test1) { + for (int i = 0; i < n; i++) { + v[i][0] = (v[i][0] - min[0]) / max[0]; + v[i][1] = (v[i][1] - min[1]) / max[1]; + } + + co[0] = (co[0] - min[0]) / max[0]; + co[1] = (co[1] - min[1]) / max[1]; + } + + for (int i = 0; i < n; i++) { + max_value = std::max(max_value, std::fabs(v[i][0] - co[0])); + max_value = std::max(max_value, std::fabs(v[i][1] - co[1])); + } + /* These to values we derived by empirically testing different values that works for the test + * files in D7772. */ + T eps, eps_sq; + + { + volatile myinterp::ignore scope; + eps = 16.0 * get_epsilon() * std::max(T(max_value), T(1.0)); + eps_sq = eps * eps; + } + + const T *v_curr, *v_next; + T2 ht_prev, ht; /* half tangents */ + T totweight = 0.0; + int i_curr, i_next; + char ix_flag = 0; + struct Double2_Len d_curr, d_next; + + /* loop over 'i_next' */ + i_curr = n - 1; + i_next = 0; + + v_curr = v[i_curr]; + v_next = v[i_next]; + + DIR_V2_SET(&d_curr, v_curr - 2 /* v[n - 2] */, co); + DIR_V2_SET(&d_next, v_curr /* v[n - 1] */, co); + ht_prev = mean_value_half_tan_v2_db(&d_curr, &d_next); + + while (i_next < n) { + /* Mark Mayer et al algorithm that is used here does not operate well if vertex is close + * to borders of face. In that case, + * do simple linear interpolation between the two edge vertices */ + + /* 'd_next.len' is in fact 'd_curr.len', just avoid copy to begin with */ + { + ignore scope; + + if (UNLIKELY(d_next.len < eps)) { + ix_flag = IS_POINT_IX; + break; + } + } + + T ret = dist_squared_to_line_segment_v2(co, v_curr, v_next); + { + ignore scope; + + if (ret < eps_sq) { + ix_flag = IS_SEGMENT_IX; + break; + } + } + + d_curr = d_next; + DIR_V2_SET(&d_next, v_next, co); + ht = mean_value_half_tan_v2_db(&d_curr, &d_next); + w[i_curr] = (d_curr.len == 0.0) ? 0.0 : T((ht_prev + ht) / d_curr.len); + totweight += w[i_curr]; + + /* step */ + i_curr = i_next++; + v_curr = v_next; + v_next = v[i_next]; + + ht_prev = ht; + } + + if (ix_flag) { + memset(w, 0, sizeof(*w) * (size_t)n); + + if (ix_flag & IS_POINT_IX) { + w[i_curr] = 1.0; + } + else { + T fac = line_point_factor_v2(co, v_curr, v_next); + CLAMP(fac, 0.0, 1.0); + w[i_curr] = 1.0 - fac; + w[i_next] = fac; + } + } + else { + if (totweight != 0.0) { + for (i_curr = 0; i_curr < n; i_curr++) { + w[i_curr] /= totweight; + } + } + } +} + +#undef IS_POINT_IX +#undef IS_SEGMENT_IX + +#undef DIR_V3_SET +#undef DIR_V2_SET + +template bool is_zero_v2(const T *a) +{ + return a[0] == 0.0f && a[1] == 0.0f; +} +template bool is_zero_v3(const T *a) +{ + return a[0] == 0.0f && a[1] == 0.0f && a[2] == 0.0f; +} + +template void copy_v3_v3(T1 r[3], const T2 b[3]) +{ + r[0] = b[0]; + r[1] = b[1]; + r[2] = b[2]; +} + +template void copy_v2_v2(T1 r[2], const T2 b[2]) +{ + r[0] = b[0]; + r[1] = b[1]; +} + +template struct TestFloat { + T f; + + static T EPSILON() + { + return get_epsilon(); + } + + TestFloat(int i) : f(T(i)) + { + check(); + } + TestFloat(float v) : f(T(v)) + { + check(); + } + TestFloat(double v) : f(T(v)) + { + check(); + } + TestFloat(const TestFloat &b) : f(b.f) + { + check(); + } + TestFloat() : f(0.0) {} + + static bool check(const float f) + { + if (ignore_check > 0) { + return true; + } + + if (f == 0.0f) { + return true; + } + + if (std::isnan(f)) { + printf("NaN!\n"); + return false; + } + + if (!std::isfinite(f)) { + printf("Infinite!\n"); + return false; + } + + if (!std::isnormal(f)) { + printf("Subnormal number!\n"); + return false; + } + + T limit = get_epsilon() * 100.0; + + if (f != get_epsilon() && f >= -limit && f <= limit) { + // printf("Really small number."); + } + + limit = 1000.0; + if (f <= -limit || f >= limit) { + // printf("Really large number."); + } + + return true; + } + + bool check() const + { + return TestFloat::check(f); + } + + explicit operator int() + { + check(); + return int(f); + } + + explicit operator float() + { + check(); + return float(f); + } + explicit operator double() + { + check(); + return double(f); + } + + TestFloat operator-() const + { + return TestFloat(-f); + } + + bool operator==(T b) const + { + check(); + TestFloat::check(b); + + return f == b; + } + + bool operator!=(T b) const + { + check(); + TestFloat::check(b); + return f != b; + } + + bool operator>=(const TestFloat &b) const + { + check(); + b.check(); + return f >= b.f; + } + bool operator<=(const TestFloat &b) const + { + check(); + b.check(); + return f <= b.f; + } + bool operator>(const TestFloat &b) const + { + check(); + b.check(); + return f > b.f; + } + bool operator>(T b) const + { + TestFloat::check(b); + return f > b; + } + bool operator<(const TestFloat &b) const + { + b.check(); + check(); + return f < b.f; + } + TestFloat operator+(const TestFloat &b) const + { + check(); + b.check(); + return TestFloat(f + b.f); + } + TestFloat operator-(const TestFloat &b) const + { + check(); + b.check(); + return TestFloat(f - b.f); + } + TestFloat operator/(const TestFloat &b) const + { + check(); + b.check(); + return TestFloat(f / b.f); + } + TestFloat operator*(const TestFloat &b) const + { + check(); + b.check(); + return TestFloat(f * b.f); + } + + const TestFloat &operator=(const TestFloat &b) + { + b.check(); + f = b.f; + check(); + return *this; + } + + const TestFloat &operator+=(const TestFloat &b) + { + b.check(); + f += b.f; + check(); + return *this; + } + const TestFloat &operator-=(const TestFloat &b) + { + b.check(); + f -= b.f; + check(); + return *this; + } + const TestFloat &operator*=(const TestFloat &b) + { + b.check(); + f *= b.f; + check(); + return *this; + } + const TestFloat &operator/=(const TestFloat &b) + { + b.check(); + f /= b.f; + check(); + return *this; + } +}; + +} // namespace myinterp + +template bool operator==(T a, myinterp::TestFloat b) +{ + myinterp::TestFloat::check(a); + b.check(); + return a == b.f; +} + +template bool operator!=(T a, myinterp::TestFloat b) +{ + myinterp::TestFloat::check(a); + b.check(); + return a != b.f; +} + +template bool operator<(T a, myinterp::TestFloat b) +{ + myinterp::TestFloat::check(a); + b.check(); + return a < b.f; +} +template bool operator>(T a, myinterp::TestFloat b) +{ + myinterp::TestFloat::check(a); + b.check(); + return a > b.f; +} + +template myinterp::TestFloat operator*(T a, myinterp::TestFloat b) +{ + myinterp::TestFloat::check(a); + b.check(); + return myinterp::TestFloat(a * b.f); +} + +template myinterp::TestFloat operator-(T a, myinterp::TestFloat b) +{ + myinterp::TestFloat::check(a); + b.check(); + return myinterp::TestFloat(a - b.f); +} + +template myinterp::TestFloat operator/(T a, myinterp::TestFloat b) +{ + myinterp::TestFloat::check(a); + b.check(); + return myinterp::TestFloat(a / b.f); +} + +namespace std { +template myinterp::TestFloat sqrt(myinterp::TestFloat f) +{ + f.check(); + return std::sqrt(f.f); +} +template myinterp::TestFloat fabs(myinterp::TestFloat f) +{ + f.check(); + return std::fabs(f.f); +} +template bool isfinite(myinterp::TestFloat f) +{ + f.check(); + return std::isfinite(f.f); +} +} // namespace std + +/* reduce algebra script + +on factor; +off period; + +comment: assumption, origin is at p; +px := 0; +py := 0; +f1 := w1*ax + w2*bx + w3*cx - px; +f2 := w1*ay + w2*by + w3*cy - py; +f3 := (w1 + w2 + w3) - 1.0; + +ff := solve({f1, f2, f3}, {w1, w2,w3}); +on fort; +fw1 := part(ff, 1, 1, 2); +fw2 := part(ff, 1, 2, 2); +fw3 := part(ff, 1, 3, 2); +off fort; + +*/ + +namespace myinterp { + +/* Distance is from the origin. */ +VEC_TEMPLATE(2) Float dist_to_line(Vec a, Vec b) +{ + Vec t = b - a; + a = -a; + + normalize_v2(t); + return t[0] * a[1] - t[1] * a[0]; +} + +VEC_TEMPLATE(2) Float line_t(Vec a, Vec b) +{ + Vec t = b - a; + a = -a; + + normalize_v2(t); + return dot_v2v2(a, t); +} + +template void tri_weights_v3_new(T *_p, T *_a, T *_b, T *_c, T *r_ws) +{ + using Vector = blender::VecBase; + + Vector p(_p); + Vector a(_a); + Vector b(_b); + Vector c(_c); + + a -= p; + b -= p; + c -= p; + + T ax = a[0], ay = a[1]; + T bx = b[0], by = b[1]; + T cx = c[0], cy = c[1]; + +#if 0 + // T cent[2] = {(a[0] + b[0] + c[0]) / 3.0f, (a[1] + b[1] + c[1]) / 3.0f}; + T div0 = len_v2v2(a, b); + + if (div0 > get_epsilon() * 1000) { + ax /= div0; + bx /= div0; + cx /= div0; + ay /= div0; + by /= div0; + cy /= div0; + } +#endif + + T div = (bx * cy - by * cx + (by - cy) * ax - (bx - cx) * ay); + float l = min_ff(min_ff(len_squared_v2v2(a, b), len_squared_v2v2(b, c)), len_squared_v2v2(c, a)); + + if (abs(div) < l * 0.1) { + /* Get distance to edges. */ + float l1 = dist_to_line(a, b); + float l2 = dist_to_line(b, c); + float l3 = dist_to_line(c, a); + float t; + + if (l1 > l2 && l1 > l3) { + t = line_t(a, b); + r_ws[0] = 1.0 - t; + r_ws[1] = t; + } + else if (l2 > l1 && l2 > l3) { + t = line_t(b, c); + r_ws[1] = 1.0 - t; + r_ws[2] = t; + } + else { + t = line_t(c, a); + r_ws[2] = 1.0 - t; + r_ws[0] = t; + } + + return; + } + + if (std::fabs(div) < get_epsilon() * 10000) { + r_ws[0] = r_ws[1] = r_ws[2] = 1.0 / 3.0; + return; + } + + r_ws[0] = (bx * cy - by * cx) / div; + r_ws[1] = (-ax * cy + ay * cx) / div; + r_ws[2] = (ax * by - ay * bx) / div; + +#if 0 + T px = a[0] * r_ws[0] + b[0] * r_ws[1] + c[0] * r_ws[2]; + T py = a[1] * r_ws[0] + b[1] * r_ws[1] + c[1] * r_ws[2]; + + printf("%.6f %.6f\n", float(px - p[0]), float(py - p[1])); +#endif +} + +template void tri_weights_v3(T *p, const T *a, const T *b, const T *c, T *r_ws) +{ + T ax = (a[0] - p[0]), ay = (a[1] - p[1]); + T bx = (b[0] - p[0]), by = (b[1] - p[1]); + T cx = (c[0] - p[0]), cy = (c[1] - p[1]); + +#if 1 + // T cent[2] = {(a[0] + b[0] + c[0]) / 3.0f, (a[1] + b[1] + c[1]) / 3.0f}; + T div0 = len_v2v2(a, b); + + if (div0 > get_epsilon() * 1000) { + ax /= div0; + bx /= div0; + cx /= div0; + ay /= div0; + by /= div0; + cy /= div0; + } +#endif + + T div = (bx * cy - by * cx + (by - cy) * ax - (bx - cx) * ay); + + bool test; + { + ignore scope; + test = std::fabs(div) < get_epsilon() * 10000; + } + + if (test) { + r_ws[0] = r_ws[1] = r_ws[2] = 1.0 / 3.0; + return; + } + + r_ws[0] = (bx * cy - by * cx) / div; + r_ws[1] = (-ax * cy + ay * cx) / div; + r_ws[2] = (ax * by - ay * bx) / div; + +#if 0 + T px = a[0] * r_ws[0] + b[0] * r_ws[1] + c[0] * r_ws[2]; + T py = a[1] * r_ws[0] + b[1] * r_ws[1] + c[1] * r_ws[2]; + + printf("%.6f %.6f\n", float(px - p[0]), float(py - p[1])); +#endif +} + +} // namespace myinterp + +template +static void interp_prop_data( + const void **src_blocks, const float *weights, int count, void *dst_block, int cd_offset) +{ + using namespace blender; + SumT sum; + + if constexpr (std::is_same_v) { + sum = 0.0f; + } + else { + sum = {}; + } + + for (int i = 0; i < count; i++) { + const T value = *static_cast(POINTER_OFFSET(src_blocks[i], cd_offset)); + + if constexpr (std::is_same_v) { + sum[0] += float(value[0]) * weights[0]; + sum[1] += float(value[1]) * weights[1]; + sum[2] += float(value[2]) * weights[2]; + sum[3] += float(value[3]) * weights[3]; + } + else { + sum += value * weights[i]; + } + } + + if (count > 0) { + T *dest = static_cast(POINTER_OFFSET(dst_block, cd_offset)); + + if constexpr (std::is_same_v) { + *dest = {uchar(sum[0]), uchar(sum[1]), uchar(sum[2]), uchar(sum[3])}; + } + else { + *dest = sum; + } + } +} + +void reproject_interp_data(CustomData *data, + const void **src_blocks, + const float *weights, + const float * /*sub_weights*/, + int count, + void *dst_block, + eCustomDataMask typemask) +{ + using namespace blender; + + for (int i = 0; i < data->totlayer; i++) { + CustomDataLayer *layer = data->layers + i; + + if (!(CD_TYPE_AS_MASK(layer->type) & typemask)) { + continue; + } + if (layer->flag & (CD_FLAG_TEMPORARY | CD_FLAG_ELEM_NOINTERP)) { + continue; + } + + switch (layer->type) { + case CD_PROP_FLOAT: + interp_prop_data(src_blocks, weights, count, dst_block, layer->offset); + break; + case CD_PROP_FLOAT2: + interp_prop_data(src_blocks, weights, count, dst_block, layer->offset); + break; + case CD_PROP_FLOAT3: + interp_prop_data(src_blocks, weights, count, dst_block, layer->offset); + break; + case CD_PROP_COLOR: + interp_prop_data(src_blocks, weights, count, dst_block, layer->offset); + break; + case CD_PROP_BYTE_COLOR: + interp_prop_data(src_blocks, weights, count, dst_block, layer->offset); + break; + } + } +} + +template // myinterp::TestFloat> +static bool reproject_bm_data(BMesh *bm, + BMLoop *l_dst, + const BMFace *f_src, + AttrDomainMask domain_mask, + eCustomDataMask typemask) +{ + using namespace myinterp; + + BMLoop *l_iter; + BMLoop *l_first; + const void **vblocks = (domain_mask & ATTR_DOMAIN_MASK_POINT) ? + static_cast(BLI_array_alloca(vblocks, f_src->len)) : + nullptr; + const void **blocks = (domain_mask & ATTR_DOMAIN_MASK_CORNER) ? + static_cast(BLI_array_alloca(blocks, f_src->len)) : + nullptr; + T(*cos_2d)[2] = static_cast(BLI_array_alloca(cos_2d, f_src->len)); + T *w = static_cast(BLI_array_alloca(w, f_src->len)); + float axis_mat[3][3]; /* use normal to transform into 2d xy coords */ + float co[2]; + + if (domain_mask == AttrDomainMask(0)) { + return false; + } + + /* Convert the 3d coords into 2d for projection. */ + float axis_dominant[3]; + if (!is_zero_v3(f_src->no)) { + BLI_assert(BM_face_is_normal_valid(f_src)); + copy_v3_v3(axis_dominant, f_src->no); + } + else { + /* Rare case in which all the vertices of the face are aligned. + * Get a random axis that is orthogonal to the tangent. */ + float vec[3]; + BM_face_calc_tangent_auto(f_src, vec); + ortho_v3_v3(axis_dominant, vec); + normalize_v3(axis_dominant); + } + axis_dominant_v3_to_m3(axis_mat, axis_dominant); + + int l_i = 0; + l_iter = l_first = BM_FACE_FIRST_LOOP(f_src); + do { + float co2d[2]; + mul_v2_m3v3(co2d, axis_mat, l_iter->v->co); + myinterp::copy_v2_v2(cos_2d[l_i], co2d); + + if (domain_mask & ATTR_DOMAIN_MASK_CORNER) { + blocks[l_i] = l_iter->head.data; + } + + float len_sq = len_squared_v3v3(l_iter->v->co, l_iter->next->v->co); + if (len_sq < FLT_EPSILON * 100.0f) { + return false; + } + + if (domain_mask & ATTR_DOMAIN_MASK_POINT) { + vblocks[l_i] = l_iter->v->head.data; + } + } while ((void)l_i++, (l_iter = l_iter->next) != l_first); + + mul_v2_m3v3(co, axis_mat, l_dst->v->co); + + T tco[2]; + myinterp::copy_v2_v2(tco, co); + + for (int i = 0; i < f_src->len; i++) { + float2 t1 = float2(cos_2d[(i + 1) % f_src->len]) - float2(cos_2d[i]); + float2 t2 = float2(cos_2d[(i + 2) % f_src->len]) - float2(cos_2d[(i + 1) % f_src->len]); + normalize_v2(t1); + normalize_v2(t2); + + float angle = blender::math::safe_acos(dot_v2v2(t1, t2)); + if (angle > M_PI * 0.95) { + return false; /* Very acute face */ + } + } + + /* interpolate */ + if (f_src->len == 3) { + myinterp::tri_weights_v3(tco, cos_2d[0], cos_2d[1], cos_2d[2], w); + T sum = 0.0; + + for (int i = 0; i < 3; i++) { + sum += w[i]; + if (w[i] < 0.0 || w[i] > 1.0) { + // return; + } + } + } + else { + myinterp::interp_weights_poly_v2(w, cos_2d, f_src->len, tco); + } + + T totw = 0.0; + for (int i = 0; i < f_src->len; i++) { + if (isnan(w[i])) { + printf("%s: NaN\n", __func__); + /* Use uniform weights. */ + totw = 0.0; + break; + } + + totw += w[i]; + } + + /* Use uniform weights in this case.*/ + if (totw == 0.0) { + for (int i = 0; i < f_src->len; i++) { + w[i] = 1.0 / T(f_src->len); + } + } + + float *fw; + + if constexpr (!std::is_same_v) { + fw = static_cast(BLI_array_alloca(fw, f_src->len)); + for (int i = 0; i < f_src->len; i++) { + fw[i] = float(w[i]); + } + } + else { + fw = w; + } + + if (domain_mask & ATTR_DOMAIN_MASK_CORNER) { + reproject_interp_data(&bm->ldata, blocks, fw, nullptr, f_src->len, l_dst->head.data, typemask); + } + + if (domain_mask & ATTR_DOMAIN_MASK_POINT) { + // bool inside = isect_point_poly_v2(co, cos_2d, l_dst->f->len, false); + reproject_interp_data( + &bm->vdata, vblocks, fw, nullptr, f_src->len, l_dst->v->head.data, typemask); + } + + return true; +} + +void BKE_sculpt_reproject_cdata(SculptSession *ss, + PBVHVertRef vertex, + float startco[3], + float startno[3], + eAttrCorrectMode undistort_mode) +{ + int boundary_flag = blender::bke::paint::vertex_attr_get(vertex, ss->attrs.boundary_flags); + if (boundary_flag & (SCULPT_BOUNDARY_UV)) { + return; + } + + BMVert *v = (BMVert *)vertex.i; + BMEdge *e; + + if (!v->e) { + return; + } + + CustomData *ldata = &ss->bm->ldata; + + int tag = BM_ELEM_TAG_ALT; + + e = v->e; + int valence = 0; + + /* First clear some flags. */ + do { + e->head.api_flag &= ~tag; + valence++; + + if (!e->l) { + continue; + } + + BMLoop *l = e->l; + do { + l->head.hflag &= ~tag; + l->next->head.hflag &= ~tag; + l->prev->head.hflag &= ~tag; + } while ((l = l->radial_next) != e->l); + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + Vector ls; + + do { + BMLoop *l = e->l; + + if (!l) { + continue; + } + + do { + BMLoop *l2 = l->v != v ? l->next : l; + + if (l2->head.hflag & tag) { + continue; + } + + l2->head.hflag |= tag; + ls.append(l2); + } while ((l = l->radial_next) != e->l); + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + /* Original (l->prev, l, l->next) projections for each loop ('l' remains unchanged). */ + + char *_blocks = static_cast(alloca(ldata->totsize * ls.size())); + void **blocks = static_cast(BLI_array_alloca(blocks, ls.size())); + + const int max_vblocks = valence * 3; + + char *_vblocks = static_cast(alloca(ss->bm->vdata.totsize * max_vblocks)); + void **vblocks = static_cast(BLI_array_alloca(vblocks, max_vblocks)); + + for (int i = 0; i < max_vblocks; i++, _vblocks += ss->bm->vdata.totsize) { + vblocks[i] = static_cast(_vblocks); + } + + for (int i = 0; i < ls.size(); i++, _blocks += ldata->totsize) { + blocks[i] = static_cast(_blocks); + } + + float vco[3], vno[3]; + + copy_v3_v3(vco, v->co); + copy_v3_v3(vno, v->no); + + BMFace _fakef, *fakef = &_fakef; + int cur_vblock = 0; + + eCustomDataMask typemask = CD_MASK_PROP_FLOAT | CD_MASK_PROP_FLOAT2 | CD_MASK_PROP_FLOAT3 | + CD_MASK_PROP_COLOR | CD_PROP_BYTE_COLOR; + + if (undistort_mode & UNDISTORT_RELAX_UVS) { + typemask &= ~CD_MASK_PROP_FLOAT2; + } + + CustomData *cdatas[2] = {&ss->bm->vdata, &ss->bm->ldata}; + bool ok = false; + + int cd_originals[4]; + cd_originals[0] = ss->attrs.orig_co->bmesh_cd_offset; + cd_originals[1] = ss->attrs.orig_no->bmesh_cd_offset; + cd_originals[2] = ss->attrs.orig_color ? ss->attrs.orig_color->bmesh_cd_offset : -1; + cd_originals[3] = ss->attrs.orig_mask ? ss->attrs.orig_mask->bmesh_cd_offset : -1; + + for (int i = 0; i < 2; i++) { + CustomData *data = cdatas[i]; + + for (int j = 0; j < data->totlayer; j++) { + if (data->layers[j].flag & (CD_FLAG_ELEM_NOINTERP)) { + continue; + } + + /* Don't reproject original data from start of stroke. */ + if (i == 0) { + bool bad = false; + + for (int k = 0; k < ARRAY_SIZE(cd_originals); k++) { + if (data->layers[j].offset == cd_originals[k]) { + bad = true; + break; + } + } + + if (bad) { + continue; + } + } + + if (CD_TYPE_AS_MASK(data->layers[j].type) & typemask) { + ok = true; + } + } + } + + /* No attributes to reproject. */ + if (!ok) { + return; + } + + Vector loops; + Vector layers; + eCustomDataMask snap_typemask = CD_MASK_PROP_FLOAT2; + AttrDomainMask domain_mask = AttrDomainMask(0); + if (undistort_mode & UNDISTORT_REPROJECT_VERTS) { + domain_mask |= ATTR_DOMAIN_MASK_POINT; + } + + if (undistort_mode & UNDISTORT_REPROJECT_CORNERS) { + domain_mask |= ATTR_DOMAIN_MASK_CORNER; + } + + for (int i = 0; i < ldata->totlayer; i++) { + CustomDataLayer *layer = ldata->layers + i; + + if (!(CD_TYPE_AS_MASK(layer->type) & snap_typemask)) { + continue; + } + if (layer->flag & CD_FLAG_ELEM_NOINTERP) { + continue; + } + layers.append(layer); + } + + e = v->e; + do { + BMLoop *l = e->l; + if (!l) { + continue; + } + + BMLoop *l2 = l; + do { + BMLoop *l3 = l2->v == v ? l2 : l2->next; + if (!loops.contains(l3)) { + loops.append(l3); + } + } while ((l2 = l2->radial_next) != e->l); + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + blender::bke::sculpt::VertLoopSnapper snapper = {loops, layers}; + + int totstep = 2; + for (int step = 0; step < totstep; step++) { + float3 startco2; + float3 startno2; + float t = (float(step) + 1.0f) / float(totstep); + + interp_v3_v3v3(startco2, v->co, startco, t); + interp_v3_v3v3(startno2, v->no, startno, t); + + normalize_v3(startno2); + + /* Build fake face with starting coordinates. */ + for (int i = 0; i < ls.size(); i++) { + BMLoop *l = ls[i]; + float no[3] = {0.0f, 0.0f, 0.0f}; + + BMLoop *fakels = static_cast(BLI_array_alloca(fakels, l->f->len)); + BMVert *fakevs = static_cast(BLI_array_alloca(fakevs, l->f->len)); + BMLoop *l2 = l->f->l_first; + BMLoop *fakel = fakels; + BMVert *fakev = fakevs; + int j = 0; + + do { + *fakel = *l2; + fakel->next = fakels + ((j + 1) % l->f->len); + fakel->prev = fakels + ((j + l->f->len - 1) % l->f->len); + + *fakev = *l2->v; + fakel->v = fakev; + + if (l2->v == v) { + copy_v3_v3(fakev->co, startco2); + copy_v3_v3(fakev->no, startno2); + add_v3_v3(no, startno2); + } + else { + add_v3_v3(no, l2->v->no); + } + + fakel++; + fakev++; + j++; + } while ((l2 = l2->next) != l->f->l_first); + + *fakef = *l->f; + fakef->l_first = fakels; + + normalize_v3(no); + + if (len_squared_v3(no) > 0.0f) { + copy_v3_v3(fakef->no, no); + } + else if (fakef->len == 4) { + normal_quad_v3( + fakef->no, l->v->co, l->next->v->co, l->next->next->v->co, l->next->next->next->v->co); + } + else { + normal_tri_v3(fakef->no, l->v->co, l->next->v->co, l->next->next->v->co); + } + + /* Interpolate. */ + BMLoop _interpl, *interpl = &_interpl; + BMVert _v = *l->v; + + *interpl = *l; + interpl->v = &_v; + +#ifdef WITH_ASAN + /* Can't us memcpy due to poisoned memory pad bytes.*/ + CustomData_bmesh_copy_data(&ss->bm->ldata, &ss->bm->ldata, l->head.data, &blocks[i]); +#else + memcpy(blocks[i], l->head.data, ss->bm->ldata.totsize); +#endif + + interpl->head.data = blocks[i]; + + interp_v3_v3v3(l->v->co, startco, vco, t); + interp_v3_v3v3(l->v->no, startno, vno, t); + normalize_v3(l->v->no); + + if (l->v == v && cur_vblock < max_vblocks) { + void *vblock = vblocks[cur_vblock]; + +#ifdef WITH_ASAN + /* Can't us memcpy due to poisoned memory pad bytes. */ + CustomData_bmesh_copy_data(&ss->bm->vdata, &ss->bm->vdata, v->head.data, &vblock); +#else + memcpy(vblock, v->head.data, ss->bm->vdata.totsize); +#endif + + interpl->v->head.data = (void *)vblock; + reproject_bm_data(ss->bm, interpl, fakef, domain_mask, typemask); + cur_vblock++; + } + else { + reproject_bm_data(ss->bm, interpl, fakef, domain_mask, typemask); + } + + copy_v3_v3(l->v->co, vco); + copy_v3_v3(l->v->no, vno); + + if (domain_mask & ATTR_DOMAIN_MASK_CORNER) { + CustomData_bmesh_copy_data( + &ss->bm->ldata, &ss->bm->ldata, interpl->head.data, &l->head.data); + } + } + + if (domain_mask & ATTR_DOMAIN_MASK_POINT && cur_vblock > 0) { + float *ws = static_cast(BLI_array_alloca(ws, cur_vblock)); + for (int i = 0; i < cur_vblock; i++) { + ws[i] = 1.0f / float(cur_vblock); + } + + float3 *origco = BM_ELEM_CD_PTR(v, ss->attrs.orig_co->bmesh_cd_offset); + float3 *origno = BM_ELEM_CD_PTR(v, ss->attrs.orig_no->bmesh_cd_offset); + + float3 origco_saved = *origco; + float3 origno_saved = *origno; + + reproject_interp_data( + &ss->bm->vdata, (const void **)vblocks, ws, nullptr, cur_vblock, v->head.data, typemask); + + *origco = origco_saved; + *origno = origno_saved; + } + + snapper.snap(); + } +} diff --git a/source/blender/blenkernel/intern/dyntopo_collapse.cc b/source/blender/blenkernel/intern/dyntopo_collapse.cc new file mode 100644 index 00000000000..19e19f00a0f --- /dev/null +++ b/source/blender/blenkernel/intern/dyntopo_collapse.cc @@ -0,0 +1,752 @@ +#include "MEM_guardedalloc.h" + +#include "DNA_customdata_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" + +#include "BLI_alloca.h" +#include "BLI_array.hh" +#include "BLI_asan.h" +#include "BLI_buffer.h" +#include "BLI_compiler_attrs.h" +#include "BLI_compiler_compat.h" +#include "BLI_index_range.hh" +#include "BLI_map.hh" +#include "BLI_math_matrix.h" +#include "BLI_math_vector.h" +#include "BLI_math_vector.hh" +#include "BLI_set.hh" +#include "BLI_task.hh" +#include "BLI_timeit.hh" +#include "BLI_utildefines.h" +#include "BLI_vector.hh" + +#include "atomic_ops.h" + +#include "BKE_customdata.hh" +#include "BKE_dyntopo.hh" +#include "BKE_paint.hh" +#include "BKE_pbvh_api.hh" +#include "BKE_sculpt.h" + +#include "../../bmesh/intern/bmesh_collapse.hh" +#include "bmesh.hh" +#include "bmesh_log.hh" + +#include "dyntopo_intern.hh" +#include "pbvh_intern.hh" + +#include +#include +#include +#include + +using blender::float2; +using blender::float3; +using blender::float4; +using blender::IndexRange; +using blender::Map; +using blender::MutableSpan; +using blender::Set; +using blender::Span; +using blender::Vector; + +namespace blender::bke::dyntopo { + +struct TraceData { + PBVH *pbvh; + BMEdge *e; + SculptSession *ss; +}; + +template static void check_new_elem_id(T *elem, PBVH *pbvh) +{ + int id = BM_ELEM_CD_GET_INT(elem, pbvh->bm_idmap->cd_id_off[int(elem->head.htype)]); + if (id != BM_ID_NONE) { + T *existing = id < pbvh->bm_idmap->map.size() ? BM_idmap_lookup(pbvh->bm_idmap, id) : + nullptr; + + if (existing) { + BM_idmap_check_assign(pbvh->bm_idmap, elem); + BM_idmap_release(pbvh->bm_idmap, existing, true); + } + + BM_idmap_assign(pbvh->bm_idmap, elem, id); + + if (existing) { + BM_idmap_check_assign(pbvh->bm_idmap, existing); + } + } + else { + BM_idmap_check_assign(pbvh->bm_idmap, elem); + } +} + +static bool vert_is_nonmanifold(BMVert *v) +{ + if (!v->e) { + return false; + } + + BMEdge *e = v->e; + do { + if (e->l && e->l->radial_next != e->l && e->l->radial_next->radial_next != e->l) { + return true; + } + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + return false; +} + +template +static void snap_corner_data( + BMesh * /*bm*/, BMEdge *e, BMVert *v_del, Span ls, int cd_offset, bool snap_midpoint) +{ + using namespace blender; + + Vector, 4> blocks; + Vector, 4> weights; + Vector, 4> loops; + Vector final_loops; + int cur_set = 0; + + for (int i : ls.index_range()) { + ls[i]->head.index = -1; + ls[i]->next->head.index = -1; /* So we can test l->next without checking if it's inside ls.*/ + } + + // XXX todo: preserve UV pins + + /* Build snapping sets (of UV vertices). */ + for (BMLoop *l1 : ls) { + if (l1->head.index != -1) { + continue; + } + + l1->head.index = cur_set; + + blocks.resize(cur_set + 1); + weights.resize(cur_set + 1); + loops.resize(cur_set + 1); + + final_loops.append(l1); + + blocks[cur_set].append(l1->head.data); + weights[cur_set].append(l1->v == v_del && !snap_midpoint ? 0.0f : 1.0f); + loops[cur_set].append(l1); + + T uv1 = *BM_ELEM_CD_PTR(l1, cd_offset); + + T uvnext = *BM_ELEM_CD_PTR(l1->next, cd_offset); + float limit = max_ff(math::distance_squared(uv1, uvnext) * 0.1f, 0.00001f); + + for (BMLoop *l2 : ls) { + T uv2 = *BM_ELEM_CD_PTR(l2, cd_offset); + + if (l2 == l1 || l2->head.index != -1) { + continue; + } + + if (math::distance_squared(uv1, uv2) < limit) { + l2->head.index = cur_set; + blocks[cur_set].append(l2->head.data); + weights[cur_set].append(l2->v == v_del && !snap_midpoint ? 0.0f : 1.0f); + loops[cur_set].append(l2); + } + } + + cur_set++; + } + + /* Merge sets */ + for (int set1 : final_loops.index_range()) { + if (!final_loops[set1]) { + continue; + } + + BMLoop *final_l = final_loops[set1]; + BMLoop *next_l = final_l->next; + + if (final_l->e != e || next_l->head.index == -1 || next_l->head.index == set1) { + continue; + } + + int set2 = next_l->head.index; + + for (void *block : blocks[set2]) { + blocks[set1].append(block); + } + for (float w : weights[set2]) { + weights[set1].append(w); + } + + for (BMLoop *l : loops[set2]) { + l->head.index = set1; + } + + /* Flag set as deleted. */ + final_loops[set2] = nullptr; + } + + /* Perform final UV snapping. */ + for (int set : final_loops.index_range()) { + BMLoop *final_l = final_loops[set]; + + if (!final_l) { + continue; + } + + float totw = 0.0f; + for (float w : weights[set]) { + totw += w; + } + + if (totw == 0.0f) { + BLI_assert_unreachable(); + + /* You never know with topology; this could happen. . .in that case + * just use uniform weights. + */ + + for (int i : weights[set].index_range()) { + weights[set][i] = 1.0f; + } + + totw = float(weights[set].size()); + } + + totw = 1.0f / totw; + for (int i : weights[set].index_range()) { + weights[set][i] *= totw; + } + + SumT sum = {}; + + /* TODO: port this code to a BMesh library function and use it in the collapse_uvs BMesh + * operator. */ + + /* Use minmax centroid. Seems like the edge weights should have summed so their centroid + * is the same as a minmax centroid,but apparently not, and this is how editmode does it. + */ +#if 1 + SumT min = {}, max = {}; + + for (int i = 0; i < SumT::type_length; i++) { + if constexpr (std::is_same_v) { + min[i] = 255; + max[i] = 0; + } + else if constexpr (std::is_same_v) { + min[i] = INT16_MAX; + max[i] = INT16_MIN; + } + else if constexpr (std::is_same_v) { + min[i] = INT32_MAX; + max[i] = INT32_MIN; + } + else if constexpr (std::is_same_v) { + min[i] = FLT_MAX; + max[i] = FLT_MIN; + } + } + + for (int i : blocks[set].index_range()) { + T value1 = *static_cast(POINTER_OFFSET(blocks[set][i], cd_offset)); + SumT value; + + if (weights[set][i] == 0.0f) { + continue; + } + + if constexpr (std::is_same_v) { + value = SumT(value1[0], value1[1], value1[2], value1[3]); + } + else { + value = value1; + } + + min = math::min(value, min); + max = math::max(value, max); + } + + sum = (min + max) * 0.5f; +#else /* Original centroid version. */ + for (int i : blocks[set].index_range()) { + T value = *static_cast(POINTER_OFFSET(blocks[set][i], cd_offset)); + + if (std::is_same_v) { + sum[0] += float(value[0]) * weights[set][i]; + sum[1] += float(value[1]) * weights[set][i]; + sum[2] += float(value[2]) * weights[set][i]; + sum[3] += float(value[3]) * weights[set][i]; + } + else { + sum += value * weights[set][i]; + } + } +#endif + + if constexpr (std::is_same_v) { + *BM_ELEM_CD_PTR(final_l, cd_offset) = sum; + } + else if constexpr (std::is_same_v) { + *BM_ELEM_CD_PTR(final_l, cd_offset) = { + uchar(sum[0]), uchar(sum[1]), uchar(sum[2]), uchar(sum[3])}; + } + } + + /* Copy snapped UVs to all loops. */ + for (BMLoop *l : ls) { + if (l->head.index == -1) { + printf("%s: Error: invalid uv set for loop\n", __func__); + continue; + } + + int set = l->head.index; + + if (final_loops[set] && l != final_loops[set]) { + *BM_ELEM_CD_PTR(l, cd_offset) = *BM_ELEM_CD_PTR(final_loops[set], cd_offset); + } + } +} + +bool pbvh_bmesh_collapse_edge_uvs( + PBVH *pbvh, BMEdge *e, BMVert *v_conn, BMVert *v_del, EdgeQueueContext *eq_ctx) +{ + BMesh *bm = pbvh->header.bm; + + pbvh_check_vert_boundary_bmesh(pbvh, v_conn); + pbvh_check_vert_boundary_bmesh(pbvh, v_del); + + int boundflag1 = BM_ELEM_CD_GET_INT(v_conn, pbvh->cd_boundary_flag); + // int boundflag2 = BM_ELEM_CD_GET_INT(v_del, pbvh->cd_boundary_flag); + + /*have to check edge flags directly, vertex flag test above isn't specific enough and + can sometimes let bad edges through*/ + if ((boundflag1 & SCULPT_BOUNDARY_SHARP_MARK) && (e->head.hflag & BM_ELEM_SMOOTH)) { + return false; + } + if ((boundflag1 & SCULPT_BOUNDARY_SEAM) && !(e->head.hflag & BM_ELEM_SEAM)) { + return false; + } + + bool snap = !(boundflag1 & SCULPTVERT_ALL_CORNER); + + /* Snap non-UV attributes. */ + if (snap) { + /* Save a few attributes we don't want to snap. */ + int ni_conn = BM_ELEM_CD_GET_INT(v_conn, pbvh->cd_vert_node_offset); + StrokeID stroke_id; + if (eq_ctx->ss->attrs.stroke_id) { + stroke_id = blender::bke::paint::vertex_attr_get({(intptr_t)v_conn}, + eq_ctx->ss->attrs.stroke_id); + } + + const float v_ws[2] = {0.5f, 0.5f}; + const void *v_blocks[2] = {v_del->head.data, v_conn->head.data}; + eCustomDataMask typemask = CD_MASK_PROP_ALL; + + CustomData_bmesh_interp_ex( + &bm->vdata, v_blocks, v_ws, nullptr, 2, v_conn->head.data, typemask); + + /* Restore node index. */ + BM_ELEM_CD_SET_INT(v_conn, pbvh->cd_vert_node_offset, ni_conn); + + /* Restore v_conn's stroke id. This is needed to avoid a nasty + * bug in the layer brush that leads to an exploding mesh. + */ + if (eq_ctx->ss->attrs.stroke_id) { + blender::bke::paint::vertex_attr_set( + {(intptr_t)v_conn}, eq_ctx->ss->attrs.stroke_id, stroke_id); + } + } + + if (!e->l) { + return snap; + } + + Vector ls; + + /* Append loops around edge first. */ + BMLoop *l = e->l; + do { + ls.append(l); + } while ((l = l->radial_next) != e->l); + + /* Now find loops around e->v1 and e->v2. */ + for (BMVert *v : std::array({e->v1, e->v2})) { + BMEdge *e = v->e; + do { + BMLoop *l = e->l; + + if (!l) { + continue; + } + + do { + BMLoop *uv_l = l->v == v ? l : l->next; + + ls.append_non_duplicates(uv_l); + } while ((l = l->radial_next) != e->l); + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + } + + for (int i : IndexRange(bm->ldata.totlayer)) { + CustomDataLayer *layer = &bm->ldata.layers[i]; + + if (layer->flag & CD_FLAG_ELEM_NOINTERP) { + continue; + } + + switch (layer->type) { + case CD_PROP_FLOAT: + snap_corner_data>(bm, e, v_del, ls, layer->offset, snap); + break; + case CD_PROP_FLOAT2: + snap_corner_data(bm, e, v_del, ls, layer->offset, snap); + break; + case CD_PROP_FLOAT3: + snap_corner_data(bm, e, v_del, ls, layer->offset, snap); + break; + case CD_PROP_COLOR: + snap_corner_data(bm, e, v_del, ls, layer->offset, snap); + break; + case CD_PROP_BYTE_COLOR: + snap_corner_data(bm, e, v_del, ls, layer->offset, snap); + break; + default: + break; + } + } + + return snap; +} + +static void on_vert_kill(void *customdata, BMVert *v) +{ + PBVH *pbvh = static_cast(customdata); + BMesh *bm = pbvh->header.bm; + + BM_log_vert_removed(bm, pbvh->bm_log, v); + pbvh_bmesh_vert_remove(pbvh, v); + BM_idmap_release(pbvh->bm_idmap, v, false); +} + +static void on_edge_kill(void *customdata, BMEdge *e) +{ + PBVH *pbvh = static_cast(customdata); + BMesh *bm = pbvh->header.bm; + dyntopo_add_flag(pbvh, e->v1, SCULPTFLAG_NEED_VALENCE); + dyntopo_add_flag(pbvh, e->v2, SCULPTFLAG_NEED_VALENCE); + + BM_log_edge_removed(bm, pbvh->bm_log, e); + BM_idmap_release(pbvh->bm_idmap, e, false); +} + +static void on_face_kill(void *customdata, BMFace *f) +{ + PBVH *pbvh = static_cast(customdata); + BMesh *bm = pbvh->header.bm; + BM_log_face_removed(bm, pbvh->bm_log, f); + pbvh_bmesh_face_remove(pbvh, f, false, true, true); + BM_idmap_release(pbvh->bm_idmap, f, false); +} + +static void on_vert_create(void *customdata, BMVert *v) +{ + PBVH *pbvh = static_cast(customdata); + BMesh *bm = pbvh->header.bm; + check_new_elem_id(v, pbvh); + pbvh_boundary_update_bmesh(pbvh, v); + dyntopo_add_flag(pbvh, v, SCULPTFLAG_NEED_VALENCE); + BM_log_vert_added(bm, pbvh->bm_log, v); +} + +static void on_edge_create(void *customdata, BMEdge *e) +{ + PBVH *pbvh = static_cast(customdata); + BMesh *bm = pbvh->header.bm; + dyntopo_add_flag(pbvh, e->v1, SCULPTFLAG_NEED_VALENCE); + dyntopo_add_flag(pbvh, e->v2, SCULPTFLAG_NEED_VALENCE); + + check_new_elem_id(e, pbvh); + pbvh_boundary_update_bmesh(pbvh, e); + BM_log_edge_added(bm, pbvh->bm_log, e); +} + +static void on_face_create(void *customdata, BMFace *f) +{ + PBVH *pbvh = static_cast(customdata); + BMesh *bm = pbvh->header.bm; + check_new_elem_id(f, pbvh); + BM_log_face_added(bm, pbvh->bm_log, f); + BM_ELEM_CD_SET_INT(f, pbvh->cd_face_node_offset, DYNTOPO_NODE_NONE); + BKE_pbvh_bmesh_add_face(pbvh, f, false, false); + + BMLoop *l = f->l_first; + do { + dyntopo_add_flag(pbvh, l->v, SCULPTFLAG_NEED_VALENCE); + pbvh_boundary_update_bmesh(pbvh, l->v); + pbvh_boundary_update_bmesh(pbvh, l->e); + } while ((l = l->next) != f->l_first); +} + +static void on_vert_combine(void *customdata, BMVert *dest, BMVert *source) +{ + PBVH *pbvh = static_cast(customdata); + /* Combine boundary flags. */ + int boundflag = BM_ELEM_CD_GET_INT(source, pbvh->cd_boundary_flag); + BM_ELEM_CD_SET_INT(dest, pbvh->cd_boundary_flag, boundflag); + + dyntopo_add_flag(pbvh, dest, SCULPTFLAG_NEED_VALENCE); +} + +static void on_edge_combine(void *customdata, BMEdge *dest, BMEdge *source) +{ + PBVH *pbvh = static_cast(customdata); + dyntopo_add_flag(pbvh, dest->v1, SCULPTFLAG_NEED_VALENCE); + dyntopo_add_flag(pbvh, dest->v2, SCULPTFLAG_NEED_VALENCE); + + /* Combine boundary flags. */ + int boundflag = BM_ELEM_CD_GET_INT(source, pbvh->cd_edge_boundary); + BM_ELEM_CD_SET_INT(dest, pbvh->cd_edge_boundary, boundflag); + + pbvh_boundary_update_bmesh(pbvh, dest->v1); + pbvh_boundary_update_bmesh(pbvh, dest->v2); +} + +void dyntopo_do_collapse(PBVH *pbvh, BMEdge *e, BMVert *v_del) +{ + + blender::bmesh::CollapseCallbacks cb; + + cb.customdata = static_cast(pbvh); + cb.on_vert_kill = on_vert_kill; + cb.on_edge_kill = on_edge_kill; + cb.on_face_kill = on_face_kill; + cb.on_vert_create = on_vert_create; + cb.on_edge_create = on_edge_create; + cb.on_face_create = on_face_create; + cb.on_vert_combine = on_vert_combine; + cb.on_edge_combine = on_edge_combine; + + blender::bmesh::join_vert_kill_edge(pbvh->header.bm, e, v_del, true, &cb); +} + +/* + * This function is rather complicated. It has to + * snap UVs, log geometry and free ids. + */ +BMVert *EdgeQueueContext::collapse_edge(PBVH *pbvh, BMEdge *e, BMVert *v1, BMVert *v2) +{ + BMVert *v_del, *v_conn; + + if (pbvh->dyntopo_stop) { + return nullptr; + } + + PBVH_CHECK_NAN(v1->co); + PBVH_CHECK_NAN(v2->co); + + TraceData tdata; + + tdata.ss = ss; + tdata.pbvh = pbvh; + tdata.e = e; + + pbvh_bmesh_check_nodes(pbvh); + + const int updateflag = SCULPTFLAG_NEED_VALENCE; + + validate_edge(pbvh, e); + + check_vert_fan_are_tris(pbvh, e->v1); + check_vert_fan_are_tris(pbvh, e->v2); + + pbvh_bmesh_check_nodes(pbvh); + + pbvh_check_vert_boundary_bmesh(pbvh, v1); + pbvh_check_vert_boundary_bmesh(pbvh, v2); + + pbvh_check_edge_boundary_bmesh(pbvh, e); + + int boundflag1 = BM_ELEM_CD_GET_INT(v1, pbvh->cd_boundary_flag); + int boundflag2 = BM_ELEM_CD_GET_INT(v2, pbvh->cd_boundary_flag); + int e_boundflag = BM_ELEM_CD_GET_INT(e, pbvh->cd_edge_boundary); + + /* Don't collapse across boundaries. */ + if ((boundflag1 & SCULPTVERT_ALL_BOUNDARY) != (boundflag2 & SCULPTVERT_ALL_BOUNDARY)) { + return nullptr; + } + + /* TODO: Sharp angle flags aren't always matching between + * verts and edges, investigate. + */ + int edge_comp_flag = SCULPTVERT_ALL_BOUNDARY & ~SCULPT_BOUNDARY_SHARP_ANGLE; + if ((boundflag1 & edge_comp_flag) != (e_boundflag & edge_comp_flag)) { + return nullptr; + } + + float w1 = mask_cb ? 1.0f - mask_cb({reinterpret_cast(v1)}, mask_cb_data) : 0.0f; + float w2 = mask_cb ? 1.0f - mask_cb({reinterpret_cast(v2)}, mask_cb_data) : 0.0f; + + bool corner1 = (boundflag1 & SCULPTVERT_ALL_CORNER) || w1 >= 0.85; + bool corner2 = (boundflag2 & SCULPTVERT_ALL_CORNER) || w2 >= 0.85; + + /* We allow two corners of the same type[s] to collapse */ + if ((boundflag1 & SCULPTVERT_ALL_CORNER) != (boundflag2 & SCULPTVERT_ALL_CORNER)) { + return nullptr; + } + + if (w1 >= 0.85 && w2 >= 0.85) { + return nullptr; + } + + /* One of the two vertices may be masked or a corner, + * select the correct one for deletion. + */ + if (corner2 && !corner1) { + v_del = v1; + v_conn = v2; + } + else { + v_del = v2; + v_conn = v1; + } + + bool non_manifold_v1 = vert_is_nonmanifold(e->v1); + bool non_manifold_v2 = vert_is_nonmanifold(e->v2); + + /* Do not collapse non-manifold verts into manifold ones. */ + if (non_manifold_v1 != non_manifold_v2) { + return nullptr; + } + + /* Make sure original data is initialized before we snap it. */ + BKE_pbvh_bmesh_check_origdata(ss, v_conn, pbvh->stroke_id); + BKE_pbvh_bmesh_check_origdata(ss, v_del, pbvh->stroke_id); + + pbvh_bmesh_check_nodes(pbvh); + + /* Snap UVS. */ + bool uvs_snapped = pbvh_bmesh_collapse_edge_uvs(pbvh, e, v_conn, v_del, this); + validate_vert(pbvh, v_conn, CHECK_VERT_FACES | CHECK_VERT_NODE_ASSIGNED); + + if (uvs_snapped) { + interp_v3_v3v3(v_conn->co, v_del->co, v_conn->co, 0.5f); + } + + /* Full non-manifold collapse. */ + dyntopo_do_collapse(pbvh, e, v_del); + + if (BM_elem_is_free((BMElem *)v_conn, BM_VERT)) { + printf("v_conn was freed\n"); + return nullptr; + } + + validate_vert(pbvh, v_conn, CHECK_VERT_FACES | CHECK_VERT_NODE_ASSIGNED); + + pbvh_boundary_update_bmesh(pbvh, v_conn); + dyntopo_add_flag(pbvh, v_conn, updateflag); + + if (!v_conn->e) { + printf("%s: pbvh error, v_conn->e was null\n", __func__); + return v_conn; + } + + if (v_conn->e) { + BMEdge *e2 = v_conn->e; + do { + validate_edge(pbvh, e2); + } while ((e2 = BM_DISK_EDGE_NEXT(e2, v_conn)) != v_conn->e); + + /* Flag boundaries for update. */ + e2 = v_conn->e; + do { + BMLoop *l = e2->l; + + pbvh_boundary_update_bmesh(pbvh, e2); + + if (!l) { + BMVert *v2 = BM_edge_other_vert(e2, v_conn); + pbvh_boundary_update_bmesh(pbvh, v2); + dyntopo_add_flag(pbvh, v2, updateflag); + continue; + } + + do { + BMLoop *l2 = l->f->l_first; + do { + pbvh_boundary_update_bmesh(pbvh, l2->v); + pbvh_boundary_update_bmesh(pbvh, l2->e); + dyntopo_add_flag(pbvh, l2->v, updateflag); + } while ((l2 = l2->next) != l->f->l_first); + } while ((l = l->radial_next) != e2->l); + } while ((e2 = BM_DISK_EDGE_NEXT(e2, v_conn)) != v_conn->e); + } + + pbvh_bmesh_check_nodes(pbvh); + + /* Destroy wire edges */ + if (v_conn->e) { + Vector es; + BMEdge *e2 = v_conn->e; + + do { + if (!e2->l) { + es.append(e2); + } + } while ((e2 = BM_DISK_EDGE_NEXT(e2, v_conn)) != v_conn->e); + + for (BMEdge *e2 : es) { + BMVert *v2 = BM_edge_other_vert(e2, v_conn); + + BM_log_edge_removed(pbvh->header.bm, pbvh->bm_log, e2); + BM_idmap_release(pbvh->bm_idmap, e2, true); + BM_edge_kill(pbvh->header.bm, e2); + + dyntopo_add_flag(pbvh, v2, SCULPTFLAG_NEED_VALENCE | SCULPTFLAG_NEED_TRIANGULATE); + BKE_sculpt_boundary_flag_update(ss, {reinterpret_cast(v2)}); + } + } + + if (!v_conn->e) { + /* Delete isolated vertex. */ + if (BM_ELEM_CD_GET_INT(v_conn, pbvh->cd_vert_node_offset) != DYNTOPO_NODE_NONE) { + blender::bke::dyntopo::pbvh_bmesh_vert_remove(pbvh, v_conn); + } + + BM_log_vert_removed(pbvh->header.bm, pbvh->bm_log, v_conn); + BM_idmap_release(pbvh->bm_idmap, v_conn, true); + BM_vert_kill(pbvh->header.bm, v_conn); + + return nullptr; + } + + if (BM_ELEM_CD_GET_INT(v_conn, pbvh->cd_vert_node_offset) == DYNTOPO_NODE_NONE) { + printf("%s: error: failed to remove vert from pbvh? v_conn->e: %p v_conn->e->l: %p\n", + __func__, + v_conn->e, + v_conn->e ? v_conn->e->l : nullptr); + } + + validate_vert(pbvh, v_conn, CHECK_VERT_FACES | CHECK_VERT_NODE_ASSIGNED); + + if (v_conn) { + check_for_fins(pbvh, v_conn); + + if (BM_elem_is_free((BMElem *)v_conn, BM_VERT)) { + v_conn = nullptr; + } + } + + validate_vert(pbvh, v_conn, CHECK_VERT_FACES | CHECK_VERT_NODE_ASSIGNED); + + if (v_conn) { + PBVH_CHECK_NAN(v_conn->co); + } + + return v_conn; +} +} // namespace blender::bke::dyntopo diff --git a/source/blender/blenkernel/intern/dyntopo_intern.hh b/source/blender/blenkernel/intern/dyntopo_intern.hh new file mode 100644 index 00000000000..c2b5f36a1df --- /dev/null +++ b/source/blender/blenkernel/intern/dyntopo_intern.hh @@ -0,0 +1,741 @@ +//#define DYNTOPO_VALIDATE_LOG + +#include "BKE_dyntopo.hh" +#include "BKE_paint.hh" +#include "BKE_pbvh_api.hh" +#include "BKE_sculpt.hh" + +#include "BLI_asan.h" +#include "BLI_heap_minmax.hh" +#include "BLI_math_vector_types.hh" +#include "BLI_rand.hh" +#include "BLI_set.hh" +#include "BLI_utildefines.h" + +#include "bmesh.hh" +#include "pbvh_intern.hh" + +#include + +using blender::float3; + +struct GHash; +struct BLI_Buffer; +struct SculptSession; + +#define DYNTOPO_DISABLE_SPLIT_EDGES 1 +#define DYNTOPO_DISABLE_FIN_REMOVAL 2 +#define DYNTOPO_DISABLE_COLLAPSE 4 +#define DYNTOPO_DISABLE_TRIANGULATOR 8 + +//#define DYNTOPO_DISABLE_FLAG \ +// (DYNTOPO_DISABLE_FIN_REMOVAL | DYNTOPO_DISABLE_COLLAPSE | DYNTOPO_DISABLE_TRIANGULATOR) +#define DYNTOPO_DISABLE_FLAG 0 + +static inline bool dyntopo_test_flag(PBVH *pbvh, BMVert *v, uint8_t flag) +{ + return *BM_ELEM_CD_PTR(v, pbvh->cd_flag) & flag; +} + +static inline void dyntopo_add_flag(PBVH *pbvh, BMVert *v, uint8_t flag) +{ + *BM_ELEM_CD_PTR(v, pbvh->cd_flag) |= flag; +} + +namespace blender::bke::dyntopo { + +enum WeightMode { + SPLIT = -1, + NONE = 0, + COLLAPSE = 1, +}; + +/* Slow. */ +//#define DYNTOPO_USE_SEP_HEAPS + +#ifdef DYNTOPO_USE_SEP_HEAPS +namespace detail { +struct EdgeWeight { + BMEdge *e; + float w; +}; + +struct max_comparator { + bool operator()(const EdgeWeight &a, const EdgeWeight &b) + { + return a.w < b.w; + } +}; + +struct min_comparator { + bool operator()(const EdgeWeight &a, const EdgeWeight &b) + { + return a.w > b.w; + } +}; +template struct EdgeHeap { + + void insert(BMEdge *e, float w) + { + queue.push({e, w}); + } + + BMEdge *top(float *r_w = nullptr) + { + const EdgeWeight &top = queue.top(); + + if (r_w) { + *r_w = top.w; + } + + return top.e; + } + + float top_weight() + { + return queue.top().w; + } + + BMEdge *pop(float *r_w = nullptr) + { + EdgeWeight top = queue.top(); + queue.pop(); + + if (r_w) { + *r_w = top.w; + } + + return top.e; + } + + bool empty() + { + return queue.empty(); + } + + private: + std::priority_queue, comparator> queue; +}; +} // namespace detail +#endif + +static int elem_sizes[] = {-1, + (int)sizeof(BMVert), + (int)sizeof(BMEdge), + 0, + (int)sizeof(BMLoop), + -1, + -1, + -1, + (int)sizeof(BMFace)}; + +inline bool bm_elem_is_free(BMElem *elem, int htype) +{ + BLI_asan_unpoison(elem, elem_sizes[htype]); + + bool ret = elem->head.htype != htype; + + if (ret) { + BLI_asan_poison(elem, elem_sizes[htype]); + } + + return ret; +} + +#ifdef DYNTOPO_VALIDATE_LOG +# define VALIDATE_LOG(log) BM_log_validate_cur(log) +#else +# define VALIDATE_LOG(log) +#endif + +//#define DYNTOPO_REPORT +//#define WITH_ADAPTIVE_CURVATURE +//#define DYNTOPO_NO_THREADING + +#define SCULPTVERT_VALENCE_TEMP SCULPTFLAG_SPLIT_TEMP + +/* Which boundary types dyntopo should respect. */ + +/* Smooth boundaries */ +#define SCULPTVERT_SMOOTH_BOUNDARY \ + (SCULPT_BOUNDARY_MESH | SCULPT_BOUNDARY_FACE_SET | SCULPT_BOUNDARY_SHARP_MARK | \ + SCULPT_BOUNDARY_SEAM | SCULPT_BOUNDARY_UV | SCULPT_BOUNDARY_SHARP_ANGLE) +#define SCULPTVERT_SMOOTH_CORNER \ + (SCULPT_CORNER_MESH | SCULPT_CORNER_FACE_SET | SCULPT_CORNER_SHARP_MARK | SCULPT_CORNER_SEAM | \ + SCULPT_CORNER_UV | SCULPT_CORNER_SHARP_ANGLE) + +/* All boundaries */ +#define SCULPTVERT_ALL_BOUNDARY \ + (SCULPT_BOUNDARY_MESH | SCULPT_BOUNDARY_FACE_SET | SCULPT_BOUNDARY_SHARP_MARK | \ + SCULPT_BOUNDARY_SEAM | SCULPT_BOUNDARY_UV | SCULPT_BOUNDARY_SHARP_ANGLE) +#define SCULPTVERT_ALL_CORNER \ + (SCULPT_CORNER_MESH | SCULPT_CORNER_FACE_SET | SCULPT_CORNER_SHARP_MARK | SCULPT_CORNER_SEAM | \ + SCULPT_CORNER_UV | SCULPT_CORNER_SHARP_ANGLE) + +#define DYNTOPO_MAX_ITER 512 + +#define DYNTOPO_USE_HEAP +#define DYNTOPO_USE_MINMAX_HEAP + +#ifndef DYNTOPO_USE_HEAP +/* don't add edges into the queue multiple times */ +# define USE_EDGEQUEUE_TAG +#endif + +/* Avoid skinny faces */ +#define USE_EDGEQUEUE_EVEN_SUBDIV + +/* How much longer we need to be to consider for subdividing + * (avoids subdividing faces which are only *slightly* skinny) */ +#define EVEN_EDGELEN_THRESHOLD 1.2f +/* How much the limit increases per recursion + * (avoids performing subdivisions too far away). */ +#define EVEN_GENERATION_SCALE 1.15f + +/* recursion depth to start applying front face test */ +#define DEPTH_START_LIMIT 4 + +//#define FANCY_EDGE_WEIGHTS <= too slow +//#define SKINNY_EDGE_FIX + +/* Slightly relax geometry by this factor along surface tangents + * to improve convergence of dyntopo remesher. This relaxation is + * applied stochastically (by skipping verts randomly) to improve + * performance. + */ +#define DYNTOPO_SAFE_SMOOTH_FAC 0.02f + +#ifdef USE_EDGEQUEUE_EVEN_SUBDIV +# include "BKE_global.hh" +#endif + +/* Support for only operating on front-faces */ +#define USE_EDGEQUEUE_FRONTFACE + +/** + * Ensure we don't have dirty tags for the edge queue, and that they are left cleared. + * (slow, even for debug mode, so leave disabled for now). + */ +#if defined(USE_EDGEQUEUE_TAG) && 0 +# if !defined(NDEBUG) +# define USE_EDGEQUEUE_TAG_VERIFY +# endif +#endif + +// #define USE_VERIFY + +#define DYNTOPO_MASK(cd_mask_offset, v) \ + (cd_mask_offset != -1 ? BM_ELEM_CD_GET_FLOAT(v, cd_mask_offset) : 0.0f) + +#ifdef USE_VERIFY +static void pbvh_bmesh_verify(PBVH *pbvh); +#endif + +/* -------------------------------------------------------------------- */ +/** \name BMesh Utility API + * + * Use some local functions which assume triangles. + * \{ */ + +/** + * Typically using BM_LOOPS_OF_VERT and BM_FACES_OF_VERT iterators are fine, + * however this is an area where performance matters so do it in-line. + * + * Take care since 'break' won't works as expected within these macros! + */ + +#define BM_DISK_EDGE(e, v) (&((&(e)->v1_disk_link)[(v) == (e)->v2])) + +#define BM_LOOPS_OF_VERT_ITER_BEGIN(l_iter_radial_, v_) \ + { \ + struct { \ + BMVert *v; \ + BMEdge *e_iter, *e_first; \ + BMLoop *l_iter_radial; \ + } _iter; \ + _iter.v = v_; \ + if (_iter.v->e) { \ + _iter.e_iter = _iter.e_first = _iter.v->e; \ + do { \ + if (_iter.e_iter->l) { \ + _iter.l_iter_radial = _iter.e_iter->l; \ + do { \ + if (_iter.l_iter_radial->v == _iter.v) { \ + l_iter_radial_ = _iter.l_iter_radial; + +#define BM_LOOPS_OF_VERT_ITER_END \ + } \ + } \ + while ((_iter.l_iter_radial = _iter.l_iter_radial->radial_next) != _iter.e_iter->l) \ + ; \ + } \ + } \ + while ((_iter.e_iter = BM_DISK_EDGE_NEXT(_iter.e_iter, _iter.v)) != _iter.e_first) \ + ; \ + } \ + } \ + ((void)0) + +#define BM_FACES_OF_VERT_ITER_BEGIN(f_iter_, v_) \ + { \ + BMLoop *l_iter_radial_; \ + BM_LOOPS_OF_VERT_ITER_BEGIN (l_iter_radial_, v_) { \ + f_iter_ = l_iter_radial_->f; + +#define BM_FACES_OF_VERT_ITER_END \ + } \ + BM_LOOPS_OF_VERT_ITER_END; \ + } \ + ((void)0) + +struct EdgeQueue; + +struct EdgeQueueContext { + blender::bke::dyntopo::BrushTester *brush_tester; + SculptSession *ss; + BLI_mempool *pool = nullptr; + BMesh *bm = nullptr; + DyntopoMaskCB mask_cb = nullptr; + void *mask_cb_data; + int cd_vert_mask_offset; + int cd_vert_node_offset; + int cd_face_node_offset; + bool local_mode; + float surface_smooth_fac; + +#ifdef DYNTOPO_USE_SEP_HEAPS + detail::EdgeHeap min_heap; + detail::EdgeHeap max_heap; +#else + blender::MinMaxHeap edge_heap; +#endif + + blender::Vector used_verts; + + float3 view_normal; + bool use_view_normal; + + float radius_squared; + float limit_len_min; + float limit_mid; + float limit_len_max; + float limit_len_min_sqr; + float limit_len_max_sqr; + + PBVHTopologyUpdateMode mode; + eAttrCorrectMode distort_correction_mode; + bool surface_relax; + + bool updatePBVH = false; + int steps[2]; + PBVH *pbvh; + + bool ignore_loop_data = false; + + bool modified = false; + int count = 0; + int _i = 0; + int curop = 0; + + EdgeQueueContext(BrushTester *brush_tester, + Object *ob, + PBVH *pbvh, + PBVHTopologyUpdateMode mode, + bool use_frontface, + float3 view_normal, + bool updatePBVH, + DyntopoMaskCB mask_cb, + void *mask_cb_data); + + void insert_val34_vert(BMVert *v); + void insert_edge(BMEdge *e, float w, WeightMode mode); + + void start(); + bool done(); + void step(); + void finish(); + + /* Remove 3 and 4 valence vertices surrounded only by triangles. */ + bool cleanup_valence_34(); + void surface_smooth(BMVert *v, float fac); + + void report(); + + bool was_flushed() + { + if (flushed_) { + flushed_ = false; + return true; + } + + return false; + } + + BMVert *collapse_edge(PBVH *pbvh, BMEdge *e, BMVert *v1, BMVert *v2); + void split_edge(BMEdge *e); + + blender::RandomNumberGenerator rand; + + /* Stochastically applies to vertices (uses a simple skip test). */ + void stochastic_smooth(BMVert *v); + + private: + template + BMEdge *pop_invalid_edges(EdgeHeapT &heap, BMEdge *in_e, float &w, bool is_max); + void step_intern(); + + bool flushed_ = false; +}; + +bool destroy_nonmanifold_fins(PBVH *pbvh, BMEdge *e_root); +bool check_face_is_tri(PBVH *pbvh, BMFace *f); +bool check_vert_fan_are_tris(PBVH *pbvh, BMVert *v); + +BMVert *pbvh_bmesh_collapse_edge( + PBVH *pbvh, BMEdge *e, BMVert *v1, BMVert *v2, struct EdgeQueueContext *eq_ctx); + +void pbvh_bmesh_vert_remove(PBVH *pbvh, BMVert *v); +inline bool bm_edge_tag_test(BMEdge *e) +{ + /* is the edge or one of its faces tagged? */ + return (BM_elem_flag_test(e->v1, BM_ELEM_TAG) || BM_elem_flag_test(e->v2, BM_ELEM_TAG) || + (e->l && + (BM_elem_flag_test(e->l->f, BM_ELEM_TAG) || + (e->l != e->l->radial_next && BM_elem_flag_test(e->l->radial_next->f, BM_ELEM_TAG))))); +} + +inline void bm_edge_tag_disable(BMEdge *e) +{ + BM_elem_flag_disable(e->v1, BM_ELEM_TAG); + BM_elem_flag_disable(e->v2, BM_ELEM_TAG); + if (e->l) { + BM_elem_flag_disable(e->l->f, BM_ELEM_TAG); + if (e->l != e->l->radial_next) { + BM_elem_flag_disable(e->l->radial_next->f, BM_ELEM_TAG); + } + } +} + +/* takes the edges loop */ +BLI_INLINE int bm_edge_is_manifold_or_boundary(BMLoop *l) +{ +#if 0 + /* less optimized version of check below */ + return (BM_edge_is_manifold(l->e) || BM_edge_is_boundary(l->e); +#else + /* if the edge is a boundary it points to its self, else this must be a manifold */ + return LIKELY(l) && LIKELY(l->radial_next->radial_next == l); +#endif +} + +inline void bm_edge_tag_enable(BMEdge *e) +{ + BM_elem_flag_enable(e->v1, BM_ELEM_TAG); + BM_elem_flag_enable(e->v2, BM_ELEM_TAG); + if (e->l) { + BM_elem_flag_enable(e->l->f, BM_ELEM_TAG); + if (e->l != e->l->radial_next) { + BM_elem_flag_enable(e->l->radial_next->f, BM_ELEM_TAG); + } + } +} + +void pbvh_bmesh_face_remove( + PBVH *pbvh, BMFace *f, bool log_face, bool check_verts, bool ensure_ownership_transfer); + +bool check_for_fins(PBVH *pbvh, BMVert *v); +void pbvh_kill_vert(PBVH *pbvh, BMVert *v, bool log_vert, bool log_edges); +BMFace *pbvh_bmesh_face_create(PBVH *pbvh, + int node_index, + BMVert *v_tri[3], + BMEdge *e_tri[3], + const BMFace *f_example, + bool ensure_verts, + bool log_face); +} // namespace blender::bke::dyntopo + +/*************************** Topology update **************************/ + +/**** Debugging Tools ********/ + +/* Note: you will have to uncomment FORCE_BMESH_CHECK in bmesh_core.cc for this + * to work in RelWithDebInfo builds. + */ +//#define CHECKMESH +namespace blender::bmesh { +int bmesh_elem_check_all(void *elem, char htype); +} + +enum eValidateVertFlags { + CHECK_VERT_NONE = 0, + CHECK_VERT_MANIFOLD = (1 << 0), + CHECK_VERT_NODE_ASSIGNED = (1 << 1), + CHECK_VERT_FACES = (1 << 2), + CHECK_VERT_ALL = (1 << 0) | (1 << 1) /* Don't include CHECK_VERT_FACES */ +}; +ENUM_OPERATORS(eValidateVertFlags, CHECK_VERT_FACES); + +enum eValidateFaceFlags { + CHECK_FACE_NONE = 0, + CHECK_FACE_NODE_ASSIGNED = (1 << 1), + CHECK_FACE_ALL = (1 << 1), + CHECK_FACE_MANIFOLD = (1 << 2), +}; +ENUM_OPERATORS(eValidateFaceFlags, CHECK_FACE_MANIFOLD); + +#ifndef CHECKMESH + +template inline bool validate_elem(PBVH *, T *) +{ + return true; +} +inline bool validate_vert(PBVH *, BMVert *, eValidateVertFlags) +{ + return true; +} +inline bool validate_edge(PBVH *, BMEdge *) +{ + return true; +} +inline bool validate_loop(PBVH *, BMLoop *) +{ + return true; +} +inline bool validate_face(PBVH *, BMFace *, eValidateFaceFlags) +{ + return true; +} +inline bool check_face_is_manifold(PBVH *, BMFace *) +{ + return true; +} +#else +# include +# include + +namespace blender::bke::dyntopo::debug { +static void debug_printf(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); +} + +template static char get_type_htype() +{ + if constexpr (std::is_same_v) { + return BM_VERT; + } + if constexpr (std::is_same_v) { + return BM_EDGE; + } + if constexpr (std::is_same_v) { + return BM_LOOP; + } + if constexpr (std::is_same_v) { + return BM_FACE; + } + + return 0; +} + +template static const char *get_type_name() +{ + if constexpr (std::is_same_v) { + return "vertex"; + } + if constexpr (std::is_same_v) { + return "edge"; + } + if constexpr (std::is_same_v) { + return "loop"; + } + if constexpr (std::is_same_v) { + return "face"; + } + + return "(invalid element)"; +} +static const char *get_type_name(char htype) +{ + switch (htype) { + case BM_VERT: + return "vertex"; + case BM_EDGE: + return "edge"; + case BM_LOOP: + return "loop"; + case BM_FACE: + return "face"; + } + + return "(invalid element)"; +} +} // namespace blender::bke::dyntopo::debug + +extern "C" const char *bm_get_error_str(int err); + +template ATTR_NO_OPT static bool validate_elem(PBVH *pbvh, T *elem) +{ + using namespace blender::bke::dyntopo::debug; + + if (!elem) { + blender::bke::dyntopo::debug::debug_printf("%s was null\n", get_type_name); + return false; + } + + if (elem->head.htype != get_type_htype()) { + blender::bke::dyntopo::debug::debug_printf( + "%p had wrong type: expected a %s but got %s (type %d).\n", + elem, + get_type_name(), + get_type_name(elem->head.htype)); + return false; + } + + int ret = blender::bmesh::bmesh_elem_check_all(static_cast(elem), get_type_htype()); + + if (ret) { + blender::bke::dyntopo::debug::debug_printf("%s (%p) failed integrity checks with code %s\n", + get_type_name(), + elem, + bm_get_error_str(ret)); + return false; + } + + return true; +} + +ATTR_NO_OPT static bool check_face_is_manifold(PBVH *pbvh, BMFace *f) +{ + using namespace blender::bke::dyntopo::debug; + + BMLoop *l = f->l_first; + do { + int count = 0; + BMLoop *l2 = l; + do { + if (count++ > 10) { + blender::bke::dyntopo::debug::debug_printf( + "Face %p has highly non-manifold edge %p\n", f, l->e); + return false; + } + } while ((l2 = l2->radial_next) != l); + } while ((l = l->next) != f->l_first); + + return true; +} + +ATTR_NO_OPT static bool validate_face(PBVH *pbvh, BMFace *f, eValidateFaceFlags flags) +{ + if (!validate_elem(pbvh, f)) { + return false; + } + + bool ok = true; + + if (flags & CHECK_FACE_MANIFOLD) { + ok = ok && check_face_is_manifold(pbvh, f); + } + + int ni = BM_ELEM_CD_GET_INT(f, pbvh->cd_face_node_offset); + if (ni < 0 || ni >= pbvh->totnode) { + if (ni != DYNTOPO_NODE_NONE || flags & CHECK_FACE_NODE_ASSIGNED) { + // blender::bke::dyntopo::debug::debug_printf("face %p has corrupted node index %d\n", f, + // ni); + return false; + } + else { + return ok; + } + } + + PBVHNode *node = &pbvh->nodes[ni]; + if (!(node->flag & PBVH_Leaf)) { + // printf("face %p has corrupted node index.", f); + return false; + } + + if (!node->bm_faces->contains(f)) { + printf("face is not in node->bm_faces.\n"); + return false; + } + + return ok; +} + +ATTR_NO_OPT static bool validate_vert(PBVH *pbvh, BMVert *v, eValidateVertFlags flags) +{ + using namespace blender::bke::dyntopo::debug; + + if (!validate_elem(pbvh, v)) { + return false; + } + + if (flags & CHECK_VERT_FACES) { + BMIter iter; + BMFace *f; + BM_ITER_ELEM (f, &iter, v, BM_FACES_OF_VERT) { + if (!validate_face(pbvh, + f, + flags & CHECK_VERT_NODE_ASSIGNED ? CHECK_FACE_NODE_ASSIGNED : + CHECK_FACE_NONE)) + { + return false; + } + } + } + + bool ok = true; + + if (flags & CHECK_VERT_MANIFOLD) { + BMIter iter; + BMFace *f; + + BM_ITER_ELEM (f, &iter, v, BM_FACES_OF_VERT) { + ok = ok && check_face_is_manifold(pbvh, f); + } + } + + int ni = BM_ELEM_CD_GET_INT(v, pbvh->cd_vert_node_offset); + + if (ni < 0 || ni >= pbvh->totnode) { + if (ni != DYNTOPO_NODE_NONE || flags & CHECK_VERT_NODE_ASSIGNED) { + // blender::bke::dyntopo::debug::debug_printf("vertex %p has corrupted node index %d\n", v, + // ni); + return false; + } + else { + return ok; + } + } + + PBVHNode *node = &pbvh->nodes[ni]; + if (!(node->flag & PBVH_Leaf)) { + // printf("Vertex %p has corrupted node index.", v); + return false; + } + + if (!node->bm_unique_verts->contains(v)) { + printf("Vertex %p is not in node->bm_unique_verts\n"); + return false; + } + if (node->bm_other_verts->contains(v)) { + printf("Vertex %p is inside of node->bm_other_verts\n"); + return false; + } + + return ok; +} + +ATTR_NO_OPT static bool validate_edge(PBVH *pbvh, BMEdge *e) +{ + return validate_elem(pbvh, e); +} +ATTR_NO_OPT static bool validate_loop(PBVH *pbvh, BMLoop *l) +{ + return validate_elem(pbvh, l); +} + +#endif diff --git a/source/blender/blenkernel/intern/mesh.cc b/source/blender/blenkernel/intern/mesh.cc index 89e968be30f..eff3e920bd8 100644 --- a/source/blender/blenkernel/intern/mesh.cc +++ b/source/blender/blenkernel/intern/mesh.cc @@ -900,7 +900,11 @@ Mesh *BKE_mesh_from_bmesh_nomain(BMesh *bm, BLI_assert(params->calc_object_remap == false); Mesh *mesh = static_cast(BKE_id_new_nomain(ID_ME, nullptr)); BM_mesh_bm_to_me(nullptr, bm, mesh, params); - BKE_mesh_copy_parameters_for_eval(mesh, me_settings); + + if (me_settings) { + BKE_mesh_copy_parameters_for_eval(mesh, me_settings); + } + return mesh; } diff --git a/source/blender/blenkernel/intern/mesh_fair.cc b/source/blender/blenkernel/intern/mesh_fair.cc index 6cf321646c8..9e50a1325df 100644 --- a/source/blender/blenkernel/intern/mesh_fair.cc +++ b/source/blender/blenkernel/intern/mesh_fair.cc @@ -249,7 +249,7 @@ class BMeshFairingContext : public FairingContext { totvert_ = bm->totvert; totloop_ = bm->totloop; - BM_mesh_elem_table_ensure(bm, BM_VERT); + BM_mesh_elem_table_ensure(bm, BM_VERT | BM_FACE); BM_mesh_elem_index_ensure(bm, BM_LOOP); /* Deformation coords. */ @@ -260,7 +260,7 @@ class BMeshFairingContext : public FairingContext { } bmloop_.reinitialize(bm->totloop); - vert_to_loop_offsets_ = Array(bm->totvert, 0); + vert_to_loop_offsets_ = Array(bm->totvert + 1, 0); vert_to_loop_indices_.reinitialize(bm->totloop); BMVert *v; @@ -280,7 +280,12 @@ class BMeshFairingContext : public FairingContext { index_iter++; } } + + vert_to_loop_offsets_[bm->totvert] = bm->totloop; + vert_to_loop_offsets_.last() = index_iter; + vlmap_ = blender::GroupedSpan(blender::OffsetIndices(vert_to_loop_offsets_), + vert_to_loop_indices_); } void adjacents_coords_from_loop(const int loop, @@ -296,7 +301,7 @@ class BMeshFairingContext : public FairingContext { BMLoop *l = bmloop_[loop]; BMVert *bmvert = BM_vert_at_index(bm, v); BMVert *bm_other_vert = BM_edge_other_vert(l->e, bmvert); - return BM_elem_index_get(bm_other_vert); + return bm_other_vert ? BM_elem_index_get(bm_other_vert) : v; } protected: diff --git a/source/blender/blenkernel/intern/paint.cc b/source/blender/blenkernel/intern/paint.cc index b201e53f6d2..facb8f080d2 100644 --- a/source/blender/blenkernel/intern/paint.cc +++ b/source/blender/blenkernel/intern/paint.cc @@ -25,19 +25,25 @@ #include "DNA_view3d_types.h" #include "DNA_workspace_types.h" +#include "BLI_alloca.h" +#include "BLI_array.h" #include "BLI_bitmap.h" #include "BLI_hash.h" +#include "BLI_index_range.hh" #include "BLI_listbase.h" #include "BLI_math_color.h" #include "BLI_math_matrix.h" #include "BLI_math_matrix.hh" #include "BLI_math_vector.h" +#include "BLI_string_ref.hh" #include "BLI_string_utf8.h" +#include "BLI_string_utils.hh" #include "BLI_utildefines.h" #include "BLI_vector.hh" #include "BLT_translation.hh" +#include "BKE_attribute.h" #include "BKE_attribute.hh" #include "BKE_brush.hh" #include "BKE_ccg.h" @@ -45,6 +51,7 @@ #include "BKE_context.hh" #include "BKE_crazyspace.hh" #include "BKE_deform.hh" +#include "BKE_global.hh" #include "BKE_gpencil_legacy.h" #include "BKE_idtype.hh" #include "BKE_image.h" @@ -63,8 +70,10 @@ #include "BKE_paint.hh" #include "BKE_pbvh_api.hh" #include "BKE_scene.hh" +#include "BKE_sculpt.hh" #include "BKE_subdiv_ccg.hh" #include "BKE_subsurf.hh" +#include "BKE_undo_system.hh" #include "DEG_depsgraph.hh" #include "DEG_depsgraph_query.hh" @@ -74,12 +83,23 @@ #include "BLO_read_write.hh" #include "bmesh.hh" +#include "bmesh_idmap.hh" +#include "bmesh_log.hh" + +using blender::bke::AttrDomain; + +// TODO: figure out bad cross module refs +namespace blender::ed::sculpt_paint::undo { +void ensure_bmlog(Object *ob); +} using blender::float3; +using blender::IndexRange; using blender::MutableSpan; using blender::Span; +using blender::StringRef; using blender::Vector; -using blender::bke::AttrDomain; +using namespace blender; static void sculpt_attribute_update_refs(Object *ob); static SculptAttribute *sculpt_attribute_ensure_ex(Object *ob, @@ -87,10 +107,13 @@ static SculptAttribute *sculpt_attribute_ensure_ex(Object *ob, eCustomDataType proptype, const char *name, const SculptAttributeParams *params, - PBVHType pbvhtype, - bool flat_array_for_bmesh); + PBVHType pbvhtype); static void sculptsession_bmesh_add_layers(Object *ob); +using blender::MutableSpan; +using blender::Span; +using blender::Vector; + static void palette_init_data(ID *id) { Palette *palette = (Palette *)id; @@ -664,7 +687,7 @@ PaintMode BKE_paintmode_get_from_tool(const bToolRef *tref) Brush *BKE_paint_brush(Paint *p) { - return (Brush *)BKE_paint_brush_for_read((const Paint *)p); + return p ? p->brush : nullptr; } const Brush *BKE_paint_brush_for_read(const Paint *p) @@ -785,6 +808,15 @@ void BKE_paint_palette_set(Paint *p, Palette *palette) } } +void BKE_paint_curve_set(Brush *br, PaintCurve *pc) +{ + if (br) { + id_us_min((ID *)br->paint_curve); + br->paint_curve = pc; + id_us_plus((ID *)br->paint_curve); + } +} + void BKE_paint_curve_clamp_endpoint_add_index(PaintCurve *pc, const int add_index) { pc->add_index = (add_index || pc->tot_points == 1) ? (add_index + 1) : 0; @@ -1124,7 +1156,7 @@ bool BKE_paint_ensure(ToolSettings *ts, Paint **r_paint) (Paint *)ts->uvsculpt, (Paint *)ts->curves_sculpt, (Paint *)&ts->imapaint)); -#ifndef NDEBUG +#ifdef DEBUG Paint paint_test = **r_paint; BKE_paint_runtime_init(ts, *r_paint); /* Swap so debug doesn't hide errors when release fails. */ @@ -1179,6 +1211,8 @@ bool BKE_paint_ensure(ToolSettings *ts, Paint **r_paint) *r_paint = paint; + paint->tile_offset[0] = paint->tile_offset[1] = paint->tile_offset[2] = 1.0f; + BKE_paint_runtime_init(ts, paint); return false; @@ -1221,7 +1255,7 @@ void BKE_paint_free(Paint *paint) MEM_SAFE_FREE(paint->tool_slots); } -void BKE_paint_copy(const Paint *src, Paint *tar, const int flag) +void BKE_paint_copy(const Paint *src, Paint *tar, int flag) { tar->brush = src->brush; tar->cavity_curve = BKE_curvemapping_copy(src->cavity_curve); @@ -1240,7 +1274,7 @@ void BKE_paint_copy(const Paint *src, Paint *tar, const int flag) void BKE_paint_stroke_get_average(const Scene *scene, const Object *ob, float stroke[3]) { - const UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; if (ups->last_stroke_valid && ups->average_stroke_counter > 0) { float fac = 1.0f / ups->average_stroke_counter; mul_v3_v3fl(stroke, ups->average_stroke_accum, fac); @@ -1291,19 +1325,9 @@ bool paint_is_grid_face_hidden(const blender::BoundedBitSpan grid_hidden, grid_hidden[(y + 1) * gridsize + x + 1] || grid_hidden[(y + 1) * gridsize + x]; } -bool paint_is_bmesh_face_hidden(const BMFace *f) +bool paint_is_bmesh_face_hidden(BMFace *f) { - BMLoop *l_iter; - BMLoop *l_first; - - l_iter = l_first = BM_FACE_FIRST_LOOP(f); - do { - if (BM_elem_flag_test(l_iter->v, BM_ELEM_HIDDEN)) { - return true; - } - } while ((l_iter = l_iter->next) != l_first); - - return false; + return BM_elem_flag_test(f, BM_ELEM_HIDDEN); } float paint_grid_paint_mask(const GridPaintMask *gpm, uint level, uint x, uint y) @@ -1315,7 +1339,7 @@ float paint_grid_paint_mask(const GridPaintMask *gpm, uint level, uint x, uint y } /* Threshold to move before updating the brush rotation, reduces jitter. */ -static float paint_rake_rotation_spacing(const UnifiedPaintSettings * /*ups*/, const Brush *brush) +static float paint_rake_rotation_spacing(UnifiedPaintSettings * /*ups*/, Brush *brush) { return brush->sculpt_tool == SCULPT_TOOL_CLAY_STRIPS ? 1.0f : 20.0f; } @@ -1346,14 +1370,29 @@ static const bool paint_rake_rotation_active(const Brush &brush, PaintMode paint bool paint_calculate_rake_rotation(UnifiedPaintSettings *ups, Brush *brush, const float mouse_pos[2], + const float initial_mouse_pos[2], PaintMode paint_mode, bool stroke_has_started) { bool ok = false; if (paint_rake_rotation_active(*brush, paint_mode)) { - float r = paint_rake_rotation_spacing(ups, brush); float rotation; + if (brush->flag & BRUSH_DRAG_DOT) { + const float dx = mouse_pos[0] - initial_mouse_pos[0]; + const float dy = mouse_pos[1] - initial_mouse_pos[1]; + + if (dx * dx + dy * dy > 0.5f) { + ups->brush_rotation = ups->brush_rotation_sec = atan2f(dx, dy) + (float)M_PI; + return true; + } + else { + return false; + } + } + + float r = paint_rake_rotation_spacing(ups, brush); + /* Use a smaller limit if the stroke hasn't started to prevent excessive pre-roll. */ if (!stroke_has_started) { r = min_ff(r, 4.0f); @@ -1387,6 +1426,130 @@ bool paint_calculate_rake_rotation(UnifiedPaintSettings *ups, return ok; } +/** + * Returns pointer to a CustomData associated with a given domain, if + * one exists. If not nullptr is returned (this may happen with e.g. + * multires and #AttrDomain::Point). + */ +static CustomData *sculpt_get_cdata(Object *ob, AttrDomain domain) +{ + SculptSession *ss = ob->sculpt; + + if (ss->bm) { + switch (domain) { + case AttrDomain::Point: + return &ss->bm->vdata; + case AttrDomain::Edge: + return &ss->bm->edata; + case AttrDomain::Corner: + return &ss->bm->ldata; + case AttrDomain::Face: + return &ss->bm->pdata; + default: + BLI_assert_unreachable(); + return nullptr; + } + } + else { + Mesh *me = BKE_object_get_original_mesh(ob); + + switch (domain) { + case AttrDomain::Point: + /* Cannot get vertex domain for multires grids. */ + if (ss->pbvh && BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { + return nullptr; + } + + return &me->vert_data; + case AttrDomain::Corner: + return &me->corner_data; + case AttrDomain::Edge: + return &me->edge_data; + case AttrDomain::Face: + return &me->face_data; + default: + BLI_assert_unreachable(); + return nullptr; + } + } +} + +static bool sculpt_boundary_flags_ensure( + Object *ob, PBVH *pbvh, int totvert, int totedge, bool force_update = false) +{ + SculptSession *ss = ob->sculpt; + bool ret = false; + + if (!ss->attrs.edge_boundary_flags) { + SculptAttributeParams params = {0}; + params.nointerp = true; + + ss->attrs.edge_boundary_flags = sculpt_attribute_ensure_ex( + ob, + AttrDomain::Edge, + CD_PROP_INT32, + SCULPT_ATTRIBUTE_NAME(edge_boundary_flags), + ¶ms, + BKE_pbvh_type(pbvh)); + + force_update = true; + ret = true; + } + + if (!ss->attrs.boundary_flags) { + SculptAttributeParams params = {0}; + params.nointerp = true; + + ss->attrs.boundary_flags = sculpt_attribute_ensure_ex(ob, + AttrDomain::Point, + CD_PROP_INT32, + SCULPT_ATTRIBUTE_NAME(boundary_flags), + ¶ms, + BKE_pbvh_type(pbvh)); + + force_update = true; + ret = true; + + BKE_pbvh_set_boundary_flags(pbvh, static_cast(ss->attrs.boundary_flags->data)); + } + + if (force_update) { + if (ss->bm) { + BM_mesh_elem_table_ensure(ss->bm, BM_VERT | BM_EDGE); + } + + for (int i = 0; i < totvert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(pbvh, i); + BKE_sculpt_boundary_flag_update(ss, vertex); + BKE_sculpt_boundary_flag_uv_update(ss, vertex); + + if (ss->pbvh) { + blender::bke::pbvh::check_vert_boundary(ss->pbvh, vertex, ss->face_sets); + } + } + + for (int i = 0; i < totedge; i++) { + PBVHEdgeRef edge = BKE_pbvh_index_to_edge(pbvh, i); + BKE_sculpt_boundary_flag_update(ss, edge); + BKE_sculpt_boundary_flag_uv_update(ss, edge); + + if (ss->pbvh) { + blender::bke::pbvh::check_edge_boundary(ss->pbvh, edge, ss->face_sets); + } + } + } + + BKE_pbvh_set_boundary_flags(pbvh, reinterpret_cast(ss->attrs.boundary_flags->data)); + + return ret; +} + +bool BKE_sculptsession_boundary_flags_ensure(Object *ob) +{ + return sculpt_boundary_flags_ensure( + ob, ob->sculpt->pbvh, BKE_sculptsession_vertex_count(ob->sculpt), ob->sculpt->totedges); +} + void BKE_sculptsession_free_deformMats(SculptSession *ss) { ss->orig_cos = {}; @@ -1409,19 +1572,18 @@ void BKE_sculptsession_free_vwpaint_data(SculptSession *ss) /** * Write out the sculpt dynamic-topology #BMesh to the #Mesh. */ -static void sculptsession_bm_to_me_update_data_only(Object *ob, bool reorder) +static void sculptsession_bm_to_me_update_data_only(Object *ob, bool /*reorder*/) { SculptSession *ss = ob->sculpt; - if (ss->bm) { - if (ob->data) { - if (reorder) { - BM_log_mesh_elems_reorder(ss->bm, ss->bm_log); - } - BMeshToMeshParams params{}; - params.calc_object_remap = false; - BM_mesh_bm_to_me(nullptr, ss->bm, static_cast(ob->data), ¶ms); - } + if (ss->bm && ob->data) { + BKE_sculptsession_update_attr_refs(ob); + + BMeshToMeshParams params = {}; + params.update_shapekey_indices = true; + params.calc_object_remap = false; + + BM_mesh_bm_to_me(nullptr, ss->bm, static_cast(ob->data), ¶ms); } } @@ -1438,7 +1600,6 @@ void BKE_sculptsession_bm_to_me(Object *ob, bool reorder) static void sculptsession_free_pbvh(Object *object) { - using namespace blender; SculptSession *ss = object->sculpt; if (!ss) { @@ -1447,6 +1608,8 @@ static void sculptsession_free_pbvh(Object *object) if (ss->pbvh) { bke::pbvh::free(ss->pbvh); + + ss->needs_pbvh_rebuild = false; ss->pbvh = nullptr; } @@ -1493,11 +1656,24 @@ void BKE_sculptsession_free(Object *ob) if (ob && ob->sculpt) { SculptSession *ss = ob->sculpt; + if (ss->bm_idmap) { + BM_idmap_destroy(ss->bm_idmap); + ss->bm_idmap = nullptr; + } + + if (ss->bm_log) { + /* Does not free the actual entries, the undo system does that */ + BM_log_free(ss->bm_log); + ss->bm_log = nullptr; + } + + /* Destroy temporary attributes. */ BKE_sculpt_attribute_destroy_temporary_all(ob); if (ss->bm) { BKE_sculptsession_bm_to_me(ob, true); BM_mesh_free(ss->bm); + ss->bm = nullptr; } sculptsession_free_pbvh(ob); @@ -1540,7 +1716,7 @@ static MultiresModifierData *sculpt_multires_modifier_get(const Scene *scene, Object *ob, const bool auto_create_mdisps) { - Mesh *mesh = (Mesh *)ob->data; + Mesh *me = (Mesh *)ob->data; ModifierData *md; VirtualModifierData virtual_modifier_data; @@ -1551,7 +1727,7 @@ static MultiresModifierData *sculpt_multires_modifier_get(const Scene *scene, bool need_mdisps = false; - if (!CustomData_get_layer(&mesh->corner_data, CD_MDISPS)) { + if (!CustomData_get_layer(&me->corner_data, CD_MDISPS)) { if (!auto_create_mdisps) { /* Multires can't work without displacement layer. */ return nullptr; @@ -1576,7 +1752,7 @@ static MultiresModifierData *sculpt_multires_modifier_get(const Scene *scene, if (mmd->sculptlvl > 0 && !(mmd->flags & eMultiresModifierFlag_UseSculptBaseMesh)) { if (need_mdisps) { - CustomData_add_layer(&mesh->corner_data, CD_MDISPS, CD_SET_DEFAULT, mesh->corners_num); + CustomData_add_layer(&me->corner_data, CD_MDISPS, CD_SET_DEFAULT, me->corners_num); } return mmd; @@ -1598,7 +1774,7 @@ MultiresModifierData *BKE_sculpt_multires_active(const Scene *scene, Object *ob) static bool sculpt_modifiers_active(Scene *scene, Sculpt *sd, Object *ob) { ModifierData *md; - Mesh *mesh = (Mesh *)ob->data; + Mesh *me = (Mesh *)ob->data; VirtualModifierData virtual_modifier_data; if (ob->sculpt->bm || BKE_sculpt_multires_active(scene, ob)) { @@ -1606,7 +1782,7 @@ static bool sculpt_modifiers_active(Scene *scene, Sculpt *sd, Object *ob) } /* Non-locked shape keys could be handled in the same way as deformed mesh. */ - if ((ob->shapeflag & OB_SHAPE_LOCK) == 0 && mesh->key && ob->shapenr) { + if ((ob->shapeflag & OB_SHAPE_LOCK) == 0 && me->key && ob->shapenr) { return true; } @@ -1639,6 +1815,58 @@ static bool sculpt_modifiers_active(Scene *scene, Sculpt *sd, Object *ob) return false; } +void BKE_sculpt_ensure_idmap(Object *ob) +{ + if (!ob->sculpt->bm_idmap) { + ob->sculpt->bm_idmap = BM_idmap_new(ob->sculpt->bm, BM_VERT | BM_EDGE | BM_FACE); + BM_idmap_check_ids(ob->sculpt->bm_idmap); + + if (ob->sculpt->bm_log) { + BM_log_set_idmap(ob->sculpt->bm_log, ob->sculpt->bm_idmap); + } + + if (ob->sculpt->pbvh) { + bke::pbvh::set_idmap(ob->sculpt->pbvh, ob->sculpt->bm_idmap); + } + + /* Push id attributes into base mesh customdata layout. */ + BKE_sculptsession_update_attr_refs(ob); + BKE_sculptsession_sync_attributes(ob, static_cast(ob->data), true); + } + else { + if (BM_idmap_check_attributes(ob->sculpt->bm_idmap)) { + BKE_sculptsession_update_attr_refs(ob); + BKE_sculptsession_sync_attributes(ob, static_cast(ob->data), true); + } + } +} + +void BKE_sculpt_distort_correction_set(Object *ob, eAttrCorrectMode value) +{ + ob->sculpt->distort_correction_mode = value; + + if (ob->sculpt->pbvh) { + BKE_pbvh_distort_correction_set(ob->sculpt->pbvh, value); + } +} + +static void sculpt_check_face_areas(Object *ob, PBVH *pbvh) +{ + SculptSession *ss = ob->sculpt; + + if (!ss->attrs.face_areas) { + SculptAttributeParams params = {0}; + + params.nointerp = true; + ss->attrs.face_areas = sculpt_attribute_ensure_ex(ob, + AttrDomain::Face, + CD_PROP_FLOAT2, + SCULPT_ATTRIBUTE_NAME(face_areas), + ¶ms, + BKE_pbvh_type(pbvh)); + } +} + /* Helper function to keep persistent base attribute references up to * date. This is a bit more tricky since they persist across strokes. */ @@ -1654,13 +1882,12 @@ static void sculpt_update_persistent_base(Object *ob) ob, AttrDomain::Point, CD_PROP_FLOAT, SCULPT_ATTRIBUTE_NAME(persistent_disp)); } -static void sculpt_update_object(Depsgraph *depsgraph, - Object *ob, - Object *ob_eval, - bool is_paint_tool) +static void sculpt_update_object( + Depsgraph *depsgraph, Object *ob, Object *ob_eval, bool /*need_pmap*/, bool is_paint_tool) { Scene *scene = DEG_get_input_scene(depsgraph); Sculpt *sd = scene->toolsettings->sculpt; + UnifiedPaintSettings &ups = scene->toolsettings->unified_paint_settings; SculptSession *ss = ob->sculpt; Mesh *mesh = BKE_object_get_original_mesh(ob); Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval); @@ -1675,8 +1902,16 @@ static void sculpt_update_object(Depsgraph *depsgraph, return; } + Brush *brush = sd->paint.brush; + ss->sharp_angle_limit = (!brush || ups.flag & UNIFIED_PAINT_FLAG_SHARP_ANGLE_LIMIT) ? + ups.sharp_angle_limit : + brush->sharp_angle_limit; + ss->smooth_boundary_flag = eSculptBoundary(ups.smooth_boundary_flag); + ss->depsgraph = depsgraph; + ss->distort_correction_mode = eAttrCorrectMode(ups.distort_correction_mode); + ss->deform_modifiers_active = sculpt_modifiers_active(scene, sd, ob); ss->building_vp_handle = false; @@ -1685,36 +1920,63 @@ static void sculpt_update_object(Depsgraph *depsgraph, ss->shapekey_active = (mmd == nullptr) ? BKE_keyblock_from_object(ob) : nullptr; + ss->material_index = (int *)CustomData_get_layer_named( + &mesh->face_data, CD_PROP_INT32, "material_index"); + /* NOTE: Weight pPaint require mesh info for loop lookup, but it never uses multires code path, * so no extra checks is needed here. */ if (mmd) { ss->multires.active = true; ss->multires.modifier = mmd; ss->multires.level = mmd->sculptlvl; - ss->totvert = mesh_eval->verts_num; - ss->faces_num = mesh_eval->faces_num; + ss->totvert = mesh->verts_num; + ss->totloops = mesh->corners_num; + ss->totedges = mesh->edges_num; + ss->faces_num = mesh->faces_num; ss->totfaces = mesh->faces_num; - /* These are assigned to the base mesh in Multires. This is needed because Face Sets operators - * and tools use the Face Sets data from the base mesh when Multires is active. */ + /* These are assigned to the base mesh in Multires. This is needed because Face Sets + * operators and tools use the Face Sets data from the base mesh when Multires is active. */ ss->vert_positions = mesh->vert_positions_for_write(); ss->faces = mesh->faces(); + ss->edges = mesh->edges(); ss->corner_verts = mesh->corner_verts(); + ss->corner_edges = mesh->corner_edges(); } else { ss->totvert = mesh->verts_num; ss->faces_num = mesh->faces_num; ss->totfaces = mesh->faces_num; + ss->totloops = mesh->corners_num; + ss->totedges = mesh->edges_num; + ss->vert_positions = mesh->vert_positions_for_write(); + ss->edges = mesh->edges(); ss->faces = mesh->faces(); + + ss->vert_positions = mesh->vert_positions_for_write(); + + ss->sharp_edge = (bool *)CustomData_get_layer_named_for_write( + &mesh->edge_data, CD_PROP_BOOL, "sharp_edge", mesh->edges_num); + ss->seam_edge = (bool *)CustomData_get_layer_named_for_write( + &mesh->edge_data, CD_PROP_BOOL, ".uv_seam", mesh->edges_num); + + ss->vdata = &mesh->vert_data; + ss->edata = &mesh->edge_data; + ss->ldata = &mesh->corner_data; + ss->pdata = &mesh->face_data; + ss->corner_verts = mesh->corner_verts(); + ss->corner_edges = mesh->corner_edges(); + ss->multires.active = false; ss->multires.modifier = nullptr; ss->multires.level = 0; CustomDataLayer *layer; AttrDomain domain; - if (BKE_pbvh_get_color_layer(mesh, &layer, &domain)) { + + if (BKE_pbvh_get_color_layer(nullptr, mesh, &layer, &domain)) { if (layer->type == CD_PROP_COLOR) { ss->vcol = static_cast(layer->data); } @@ -1734,24 +1996,76 @@ static void sculpt_update_object(Depsgraph *depsgraph, } } + CustomData *ldata; + if (ss->bm) { + ldata = &ss->bm->ldata; + } + else { + ldata = &mesh->corner_data; + } + + ss->totuv = 0; + for (int i : IndexRange(ldata->totlayer)) { + CustomDataLayer &layer = ldata->layers[i]; + if (layer.type == CD_PROP_FLOAT2 && !(layer.flag & CD_FLAG_TEMPORARY)) { + ss->totuv++; + } + } + + ss->hide_poly = (bool *)CustomData_get_layer_named_for_write( + &mesh->face_data, CD_PROP_BOOL, ".hide_poly", mesh->faces_num); + + ss->subdiv_ccg = mesh_eval->runtime->subdiv_ccg.get(); + + PBVH *pbvh = BKE_sculpt_object_pbvh_ensure(depsgraph, ob); + sculpt_check_face_areas(ob, pbvh); + + if (ss->bm) { + ss->totedges = ss->bm->totedge; + } + + if (pbvh) { + blender::bke::pbvh::sharp_limit_set(pbvh, ss->sharp_angle_limit); + } + /* Sculpt Face Sets. */ if (use_face_sets) { - ss->face_sets = static_cast( - CustomData_get_layer_named(&mesh->face_data, CD_PROP_INT32, ".sculpt_face_set")); + int *face_sets = static_cast(CustomData_get_layer_named_for_write( + &mesh->face_data, CD_PROP_INT32, ".sculpt_face_set", mesh->faces_num)); + + if (face_sets) { + /* Load into sculpt attribute system. */ + ss->face_sets = BKE_sculpt_face_sets_ensure(ob); + } + else { + ss->face_sets = nullptr; + } } else { ss->face_sets = nullptr; } - ss->hide_poly = (bool *)CustomData_get_layer_named(&mesh->face_data, CD_PROP_BOOL, ".hide_poly"); + sculpt_boundary_flags_ensure(ob, pbvh, BKE_sculptsession_vertex_count(ss), ss->totedges); + + BKE_pbvh_update_active_vcol(pbvh, mesh); + + if (BKE_pbvh_type(pbvh) == PBVH_FACES) { + ss->poly_normals = blender::bke::pbvh::get_poly_normals(ss->pbvh); + } + else { + ss->poly_normals = {}; + } ss->subdiv_ccg = mesh_eval->runtime->subdiv_ccg.get(); - PBVH *pbvh = BKE_sculpt_object_pbvh_ensure(depsgraph, ob); BLI_assert(pbvh == ss->pbvh); UNUSED_VARS_NDEBUG(pbvh); - BKE_pbvh_subdiv_cgg_set(ss->pbvh, ss->subdiv_ccg); + if (ss->subdiv_ccg) { + BKE_pbvh_subdiv_ccg_set(ss->pbvh, ss->subdiv_ccg); + } + + BKE_pbvh_update_hide_attributes_from_mesh(ss->pbvh); sculpt_attribute_update_refs(ob); sculpt_update_persistent_base(ob); @@ -1761,7 +2075,7 @@ static void sculpt_update_object(Depsgraph *depsgraph, } if (ss->pbvh) { - BKE_pbvh_pmap_set(ss->pbvh, ss->vert_to_face_map); + blender::bke::pbvh::set_pmap(ss->pbvh, ss->vert_to_face_map); } if (ss->deform_modifiers_active) { @@ -1772,7 +2086,8 @@ static void sculpt_update_object(Depsgraph *depsgraph, Mesh *me_eval_deform = ob_eval->runtime->mesh_deform_eval; /* If the fully evaluated mesh has the same topology as the deform-only version, use it. - * This matters because crazyspace evaluation is very restrictive and excludes even modifiers + * This matters because crazyspace evaluation is very restrictive and excludes even + * modifiers * that simply recompute vertex weights (which can even include Geometry Nodes). */ if (me_eval_deform->faces_num == mesh_eval->faces_num && me_eval_deform->corners_num == mesh_eval->corners_num && @@ -1780,7 +2095,7 @@ static void sculpt_update_object(Depsgraph *depsgraph, { BKE_sculptsession_free_deformMats(ss); - BLI_assert(me_eval_deform->verts_num == mesh->verts_num); + BLI_assert(me_eval_deform->totvert == mesh->verts_num); ss->deform_cos = mesh_eval->vert_positions(); BKE_pbvh_vert_coords_apply(ss->pbvh, ss->deform_cos); @@ -1800,8 +2115,23 @@ static void sculpt_update_object(Depsgraph *depsgraph, BKE_crazyspace_build_sculpt(depsgraph, scene, ob, ss->deform_imats, ss->deform_cos); BKE_pbvh_vert_coords_apply(ss->pbvh, ss->deform_cos); + int a = 0; for (blender::float3x3 &matrix : ss->deform_imats) { + float *co = ss->deform_cos[a]; + matrix = blender::math::invert(matrix); + + float ff = dot_v3v3(co, co); + if (isnan(ff) || !isfinite(ff)) { + printf("%s: nan1! %.4f %.4f %.4f\n", __func__, co[0], co[1], co[2]); + } + + ff = blender::math::determinant(matrix); + if (isnan(ff) || !isfinite(ff)) { + printf("%s: nan2!\n", __func__); + } + + a++; } } } @@ -1831,6 +2161,128 @@ static void sculpt_update_object(Depsgraph *depsgraph, } } + int totvert = 0; + + switch (BKE_pbvh_type(pbvh)) { + case PBVH_FACES: + totvert = mesh->verts_num; + break; + case PBVH_BMESH: + totvert = ss->bm ? ss->bm->totvert : mesh->verts_num; + break; + case PBVH_GRIDS: + totvert = BKE_pbvh_get_grid_num_verts(ss->pbvh); + break; + } + + BKE_sculpt_init_flags_valence(ob, pbvh, totvert, false); + + if (ss->bm && mesh->key && ob->shapenr != ss->bm->shapenr) { + KeyBlock *actkey = static_cast( + BLI_findlink(&mesh->key->block, ss->bm->shapenr - 1)); + KeyBlock *newkey = static_cast(BLI_findlink(&mesh->key->block, ob->shapenr - 1)); + + bool updatePBVH = false; + + if (!actkey) { + printf("%s: failed to find active shapekey\n", __func__); + if (!ss->bm->shapenr || !CustomData_has_layer(&ss->bm->vdata, CD_SHAPEKEY)) { + printf("allocating shapekeys. . .\n"); + + // need to allocate customdata for keys + for (KeyBlock *key = (KeyBlock *)mesh->key->block.first; key; key = key->next) { + + int idx = CustomData_get_named_layer_index(&ss->bm->vdata, CD_SHAPEKEY, key->name); + + if (idx == -1) { + BM_data_layer_add_named(ss->bm, &ss->bm->vdata, CD_SHAPEKEY, key->name); + BKE_sculptsession_update_attr_refs(ob); + + idx = CustomData_get_named_layer_index(&ss->bm->vdata, CD_SHAPEKEY, key->name); + ss->bm->vdata.layers[idx].uid = key->uid; + } + + int cd_shapeco = ss->bm->vdata.layers[idx].offset; + BMVert *v; + BMIter iter; + + BM_ITER_MESH (v, &iter, ss->bm, BM_VERTS_OF_MESH) { + float *keyco = (float *)BM_ELEM_CD_GET_VOID_P(v, cd_shapeco); + + copy_v3_v3(keyco, v->co); + } + } + } + + updatePBVH = true; + ss->bm->shapenr = ob->shapenr; + } + + if (!newkey) { + printf("%s: failed to find new active shapekey\n", __func__); + } + + if (actkey && newkey) { + int cd_co1 = CustomData_get_named_layer_index(&ss->bm->vdata, CD_SHAPEKEY, actkey->name); + int cd_co2 = CustomData_get_named_layer_index(&ss->bm->vdata, CD_SHAPEKEY, newkey->name); + + BMVert *v; + BMIter iter; + + if (cd_co1 == -1) { // non-recoverable error + printf("%s: failed to find active shapekey in customdata.\n", __func__); + return; + } + else if (cd_co2 == -1) { + printf("%s: failed to find new shapekey in customdata; allocating . . .\n", __func__); + + BM_data_layer_add_named(ss->bm, &ss->bm->vdata, CD_SHAPEKEY, newkey->name); + int idx = CustomData_get_named_layer_index(&ss->bm->vdata, CD_SHAPEKEY, newkey->name); + + int cd_co = ss->bm->vdata.layers[idx].offset; + ss->bm->vdata.layers[idx].uid = newkey->uid; + + BKE_sculptsession_update_attr_refs(ob); + + BM_ITER_MESH (v, &iter, ss->bm, BM_VERTS_OF_MESH) { + float *keyco = (float *)BM_ELEM_CD_GET_VOID_P(v, cd_co); + copy_v3_v3(keyco, v->co); + } + + cd_co2 = idx; + } + + cd_co1 = ss->bm->vdata.layers[cd_co1].offset; + cd_co2 = ss->bm->vdata.layers[cd_co2].offset; + + BM_ITER_MESH (v, &iter, ss->bm, BM_VERTS_OF_MESH) { + float *co1 = (float *)BM_ELEM_CD_GET_VOID_P(v, cd_co1); + float *co2 = (float *)BM_ELEM_CD_GET_VOID_P(v, cd_co2); + + copy_v3_v3(co1, v->co); + copy_v3_v3(v->co, co2); + } + + ss->bm->shapenr = ob->shapenr; + + updatePBVH = true; + } + + if (updatePBVH && ss->pbvh) { + Vector nodes = blender::bke::pbvh::get_flagged_nodes(ss->pbvh, PBVH_Leaf); + + for (PBVHNode *node : nodes) { + BKE_pbvh_node_mark_update(node); + BKE_pbvh_vert_tag_update_normal_tri_area(node); + } + } + } + + if (ss->bm_log && ss->pbvh) { + bke::pbvh::set_idmap(ss->pbvh, ss->bm_idmap); + BKE_pbvh_set_bm_log(ss->pbvh, ss->bm_log); + } + if (is_paint_tool) { if (ss->vcol_domain == AttrDomain::Corner) { /* Ensure pbvh nodes have loop indices; the sculpt undo system @@ -1865,6 +2317,12 @@ static void sculpt_update_object(Depsgraph *depsgraph, BKE_texpaint_slots_refresh_object(scene, ob); } } + + if (ss->pbvh) { + blender::bke::pbvh::set_flags_valence(ss->pbvh, + static_cast(ss->attrs.flags->data), + static_cast(ss->attrs.valence->data)); + } } void BKE_sculpt_update_object_before_eval(Object *ob_eval) @@ -1872,8 +2330,8 @@ void BKE_sculpt_update_object_before_eval(Object *ob_eval) /* Update before mesh evaluation in the dependency graph. */ SculptSession *ss = ob_eval->sculpt; - if (ss && ss->building_vp_handle == false) { - if (!ss->cache && !ss->filter_cache && !ss->expand_cache) { + if (ss && (ss->building_vp_handle == false || ss->needs_pbvh_rebuild)) { + if (ss->needs_pbvh_rebuild || (!ss->cache && !ss->filter_cache && !ss->expand_cache)) { /* We free pbvh on changes, except in the middle of drawing a stroke * since it can't deal with changing PVBH node organization, we hope * topology does not change in the meantime .. weak. */ @@ -1899,8 +2357,13 @@ void BKE_sculpt_update_object_after_eval(Depsgraph *depsgraph, Object *ob_eval) /* Update after mesh evaluation in the dependency graph, to rebuild PBVH or * other data when modifiers change the mesh. */ Object *ob_orig = DEG_get_original_object(ob_eval); + Mesh *me_orig = BKE_object_get_original_mesh(ob_orig); - sculpt_update_object(depsgraph, ob_orig, ob_eval, false); + if (ob_orig->sculpt) { + BKE_sculptsession_sync_attributes(ob_orig, me_orig, false); + } + + sculpt_update_object(depsgraph, ob_orig, ob_eval, false, false); } void BKE_sculpt_color_layer_create_if_needed(Object *object) @@ -1909,7 +2372,10 @@ void BKE_sculpt_color_layer_create_if_needed(Object *object) using namespace blender::bke; Mesh *orig_me = BKE_object_get_original_mesh(object); - if (orig_me->attributes().contains(orig_me->active_color_attribute)) { + SculptAttribute attr = BKE_sculpt_find_attribute(object, orig_me->active_color_attribute); + if (!attr.is_empty() && (CD_TYPE_AS_MASK(attr.proptype) & CD_MASK_COLOR_ALL) && + ELEM(attr.domain, AttrDomain::Point, AttrDomain::Corner)) + { return; } @@ -1928,22 +2394,83 @@ void BKE_sculpt_color_layer_create_if_needed(Object *object) if (object->sculpt && object->sculpt->pbvh) { BKE_pbvh_update_active_vcol(object->sculpt->pbvh, orig_me); } + + /* Flush attribute into sculpt mesh. */ + BKE_sculptsession_sync_attributes(object, orig_me, false); } void BKE_sculpt_update_object_for_edit(Depsgraph *depsgraph, Object *ob_orig, bool is_paint_tool) { - BLI_assert(ob_orig == DEG_get_original_object(ob_orig)); - + /* Update from sculpt operators and undo, to update sculpt session + * and PBVH after edits. */ Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob_orig); - sculpt_update_object(depsgraph, ob_orig, ob_eval, is_paint_tool); + sculpt_update_object(depsgraph, ob_orig, ob_eval, true, is_paint_tool); +} + +int *BKE_sculpt_face_sets_ensure(Object *ob) +{ + SculptSession *ss = ob->sculpt; + + if (!ss->attrs.face_set) { + SculptAttributeParams params = {}; + params.permanent = true; + + CustomData *cdata = sculpt_get_cdata(ob, AttrDomain::Face); + bool clear = CustomData_get_named_layer_index( + cdata, CD_PROP_INT32, SCULPT_ATTRIBUTE_NAME(face_set)) == -1; + + ss->attrs.face_set = BKE_sculpt_attribute_ensure( + ob, AttrDomain::Face, CD_PROP_INT32, SCULPT_ATTRIBUTE_NAME(face_set), ¶ms); + + if (clear) { + if (ss->bm) { + BMFace *f; + BMIter iter; + int cd_faceset = ss->attrs.face_set->bmesh_cd_offset; + + BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { + BM_ELEM_CD_SET_INT(f, cd_faceset, 1); + } + } + else { + int *face_sets = static_cast(ss->attrs.face_set->data); + + for (int i : IndexRange(ss->totfaces)) { + face_sets[i] = 1; + } + } + + Mesh *mesh = static_cast(ob->data); + mesh->face_sets_color_default = 1; + } + } + + int *face_sets = static_cast(ss->attrs.face_set->data); + ss->face_sets = face_sets; + + return face_sets; +} + +bool *BKE_sculpt_hide_poly_ensure(Object *ob) +{ + SculptAttributeParams params = {0}; + params.permanent = true; + + ob->sculpt->attrs.hide_poly = BKE_sculpt_attribute_ensure( + ob, AttrDomain::Face, CD_PROP_BOOL, ".hide_poly", ¶ms); + + bool *hide_poly = static_cast(ob->sculpt->attrs.hide_poly->data); + ob->sculpt->hide_poly = hide_poly; + + return hide_poly; } void BKE_sculpt_hide_poly_pointer_update(Object &object) { - const Mesh &mesh = *static_cast(object.data); - object.sculpt->hide_poly = static_cast( - CustomData_get_layer_named(&mesh.face_data, CD_PROP_BOOL, ".hide_poly")); + Mesh &mesh = *static_cast(object.data); + object.sculpt->hide_poly = static_cast(CustomData_get_layer_named_for_write( + &mesh.face_data, CD_PROP_BOOL, ".hide_poly", mesh.faces_num)); } void BKE_sculpt_mask_layers_ensure(Depsgraph *depsgraph, @@ -1953,22 +2480,22 @@ void BKE_sculpt_mask_layers_ensure(Depsgraph *depsgraph, { using namespace blender; using namespace blender::bke; - Mesh *mesh = static_cast(ob->data); - const OffsetIndices faces = mesh->faces(); - const Span corner_verts = mesh->corner_verts(); - MutableAttributeAccessor attributes = mesh->attributes_for_write(); + Mesh *me = static_cast(ob->data); + const OffsetIndices faces = me->faces(); + const Span corner_verts = me->corner_verts(); + MutableAttributeAccessor attributes = me->attributes_for_write(); /* if multires is active, create a grid paint mask layer if there * isn't one already */ - if (mmd && !CustomData_has_layer(&mesh->corner_data, CD_GRID_PAINT_MASK)) { + if (mmd && !CustomData_has_layer(&me->corner_data, CD_GRID_PAINT_MASK)) { int level = max_ii(1, mmd->sculptlvl); int gridsize = BKE_ccg_gridsize(level); int gridarea = gridsize * gridsize; GridPaintMask *gmask = static_cast(CustomData_add_layer( - &mesh->corner_data, CD_GRID_PAINT_MASK, CD_SET_DEFAULT, mesh->corners_num)); + &me->corner_data, CD_GRID_PAINT_MASK, CD_SET_DEFAULT, me->corners_num)); - for (int i = 0; i < mesh->corners_num; i++) { + for (int i = 0; i < me->corners_num; i++) { GridPaintMask *gpm = &gmask[i]; gpm->level = level; @@ -1993,8 +2520,8 @@ void BKE_sculpt_mask_layers_ensure(Depsgraph *depsgraph, for (const int corner : face) { GridPaintMask *gpm = &gmask[corner]; const int vert = corner_verts[corner]; - const int prev = corner_verts[mesh::face_corner_prev(face, corner)]; - const int next = corner_verts[mesh::face_corner_next(face, corner)]; + const int prev = corner_verts[mesh::face_corner_prev(face, vert)]; + const int next = corner_verts[mesh::face_corner_next(face, vert)]; gpm->data[0] = avg; gpm->data[1] = (mask_span[vert] + mask_span[next]) * 0.5f; @@ -2015,6 +2542,10 @@ void BKE_sculpt_mask_layers_ensure(Depsgraph *depsgraph, /* The evaluated mesh must be updated to contain the new data. */ DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); } + + if (ob->sculpt) { + BKE_sculptsession_update_attr_refs(ob); + } } void BKE_sculpt_toolsettings_data_ensure(Scene *scene) @@ -2037,14 +2568,14 @@ void BKE_sculpt_toolsettings_data_ensure(Scene *scene) sd->automasking_view_normal_falloff = defaults->automasking_view_normal_limit; } - if (sd->detail_percent == 0.0f) { - sd->detail_percent = defaults->detail_percent; + if (sd->dyntopo.detail_percent == 0.0f) { + sd->dyntopo.detail_percent = defaults->detail_percent; } - if (sd->constant_detail == 0.0f) { - sd->constant_detail = defaults->constant_detail; + if (sd->dyntopo.constant_detail == 0.0f) { + sd->dyntopo.constant_detail = defaults->constant_detail; } - if (sd->detail_size == 0.0f) { - sd->detail_size = defaults->detail_size; + if (sd->dyntopo.detail_size == 0.0f) { + sd->dyntopo.detail_size = defaults->detail_size; } /* Set sane default tiling offsets. */ @@ -2115,22 +2646,85 @@ void BKE_sculpt_sync_face_visibility_to_grids(Mesh *mesh, SubdivCCG *subdiv_ccg) }); } -namespace blender::bke { - -static PBVH *build_pbvh_for_dynamic_topology(Object *ob) +static PBVH *build_pbvh_for_dynamic_topology(Object *ob, bool update_flags_valence) { - sculptsession_bmesh_add_layers(ob); + SculptSession *ss = ob->sculpt; + PBVH *pbvh = ss->pbvh = BKE_pbvh_new(PBVH_BMESH); - return pbvh::build_bmesh(ob->sculpt->bm, - ob->sculpt->bm_log, - ob->sculpt->attrs.dyntopo_node_id_vertex->bmesh_cd_offset, - ob->sculpt->attrs.dyntopo_node_id_face->bmesh_cd_offset); + BKE_pbvh_set_bmesh(pbvh, ss->bm); + BM_mesh_elem_table_ensure(ss->bm, BM_VERT | BM_EDGE | BM_FACE); + + sculptsession_bmesh_add_layers(ob); + sculpt_boundary_flags_ensure(ob, pbvh, ss->bm->totvert, ss->bm->totedge); + BKE_sculpt_ensure_sculpt_layers(ob); + + if (update_flags_valence) { + BKE_sculpt_init_flags_valence(ob, ss->pbvh, ss->bm->totvert, true); + } + + BKE_sculptsession_update_attr_refs(ob); + + BKE_sculpt_ensure_origco(ob); + sculpt_check_face_areas(ob, pbvh); + + BKE_sculpt_ensure_idmap(ob); + blender::bke::pbvh::sharp_limit_set(pbvh, ss->sharp_angle_limit); + + bke::pbvh::build_bmesh(pbvh, + BKE_object_get_original_mesh(ob), + ss->bm, + ss->bm_log, + ss->bm_idmap, + ss->attrs.dyntopo_node_id_vertex->bmesh_cd_offset, + ss->attrs.dyntopo_node_id_face->bmesh_cd_offset, + ss->attrs.face_areas->bmesh_cd_offset, + ss->attrs.boundary_flags->bmesh_cd_offset, + ss->attrs.edge_boundary_flags->bmesh_cd_offset, + ss->attrs.flags ? ss->attrs.flags->bmesh_cd_offset : -1, + ss->attrs.valence ? ss->attrs.valence->bmesh_cd_offset : -1, + ss->attrs.orig_co ? ss->attrs.orig_co->bmesh_cd_offset : -1, + ss->attrs.orig_no ? ss->attrs.orig_no->bmesh_cd_offset : -1); + + if (ss->bm_log) { + BKE_pbvh_set_bm_log(pbvh, ss->bm_log); + } + + return pbvh; } static PBVH *build_pbvh_from_regular_mesh(Object *ob, Mesh *me_eval_deform) { - Mesh *mesh = BKE_object_get_original_mesh(ob); - PBVH *pbvh = pbvh::build_mesh(mesh); + SculptSession *ss = ob->sculpt; + Mesh *me = BKE_object_get_original_mesh(ob); + + if (ss->vert_to_face_map.is_empty()) { + ss->vert_to_face_map = me->vert_to_face_map(); + } + + PBVH *pbvh = ob->sculpt->pbvh = bke::pbvh::build_mesh(me); + + BKE_sculptsession_update_attr_refs(ob); + + blender::bke::pbvh::set_pmap(ss->pbvh, ss->vert_to_face_map); + BKE_sculpt_ensure_sculpt_layers(ob); + BKE_sculpt_init_flags_valence(ob, pbvh, me->verts_num, true); + BKE_sculpt_ensure_origco(ob); + + Mesh *mesh = static_cast(ob->data); + Span positions = mesh->vert_positions(); + Span normals = mesh->vert_normals(); + + for (int i = 0; i < mesh->verts_num; i++) { + blender::bke::paint::vertex_attr_set({i}, ss->attrs.orig_co, positions[i]); + blender::bke::paint::vertex_attr_set({i}, ss->attrs.orig_no, normals[i]); + } + + sculpt_check_face_areas(ob, pbvh); + BKE_sculptsession_update_attr_refs(ob); + + blender::bke::sculpt::sculpt_vert_boundary_ensure(ob); + + blender::bke::pbvh::sharp_limit_set(pbvh, ss->sharp_angle_limit); const bool is_deformed = check_sculpt_object_deformed(ob, true); if (is_deformed && me_eval_deform != nullptr) { @@ -2142,36 +2736,101 @@ static PBVH *build_pbvh_from_regular_mesh(Object *ob, Mesh *me_eval_deform) static PBVH *build_pbvh_from_ccg(Object *ob, SubdivCCG *subdiv_ccg) { - const CCGKey key = BKE_subdiv_ccg_key_top_level(*subdiv_ccg); + SculptSession *ss = ob->sculpt; + + CCGKey key = BKE_subdiv_ccg_key_top_level(*subdiv_ccg); + PBVH *pbvh = ob->sculpt->pbvh = BKE_pbvh_new(PBVH_GRIDS); + Mesh *base_mesh = BKE_mesh_from_object(ob); + BKE_sculpt_sync_face_visibility_to_grids(base_mesh, subdiv_ccg); - return pbvh::build_grids(&key, base_mesh, subdiv_ccg); + BKE_sculptsession_update_attr_refs(ob); + sculpt_check_face_areas(ob, pbvh); + blender::bke::sculpt::sculpt_vert_boundary_ensure(ob); + + blender::bke::pbvh::build_grids(&key, base_mesh, subdiv_ccg); + blender::bke::pbvh::sharp_limit_set(pbvh, ss->sharp_angle_limit); + + if (ss->vert_to_face_map.is_empty()) { + ss->vert_to_face_map = base_mesh->vert_to_face_map(); + } + + blender::bke::pbvh::set_pmap(ss->pbvh, ss->vert_to_face_map); + int totvert = BKE_pbvh_get_grid_num_verts(pbvh); + BKE_sculpt_init_flags_valence(ob, pbvh, totvert, true); + + BKE_subdiv_ccg_start_face_grid_index_ensure(*ss->subdiv_ccg); + + return pbvh; } -} // namespace blender::bke +bool BKE_sculpt_init_flags_valence(Object *ob, struct PBVH *pbvh, int totvert, bool reset_flags) +{ + SculptSession *ss = ob->sculpt; + + if (!ss->attrs.flags) { + BKE_sculpt_ensure_sculpt_layers(ob); + + reset_flags = true; + } + + BKE_sculpt_ensure_origco(ob); + sculpt_boundary_flags_ensure(ob, pbvh, totvert, ss->totedges); + BKE_sculptsession_update_attr_refs(ob); + + if (reset_flags) { + if (ss->bm) { + int cd_flags = ss->attrs.flags->bmesh_cd_offset; + BMVert *v; + BMIter iter; + + BM_ITER_MESH (v, &iter, ss->bm, BM_VERTS_OF_MESH) { + *BM_ELEM_CD_PTR(v, cd_flags) = SCULPTFLAG_NEED_VALENCE | + SCULPTFLAG_NEED_TRIANGULATE; + } + } + else { + uint8_t *flags = static_cast(ss->attrs.flags->data); + + for (int i = 0; i < totvert; i++) { + flags[i] = SCULPTFLAG_NEED_VALENCE | SCULPTFLAG_NEED_TRIANGULATE; + } + } + } + + blender::bke::pbvh::set_flags_valence(ss->pbvh, + static_cast(ss->attrs.flags->data), + static_cast(ss->attrs.valence->data)); + + return false; +} PBVH *BKE_sculpt_object_pbvh_ensure(Depsgraph *depsgraph, Object *ob) { - using namespace blender::bke; if (ob->sculpt == nullptr) { return nullptr; } - PBVH *pbvh = ob->sculpt->pbvh; + SculptSession *ss = ob->sculpt; + + PBVH *pbvh = ss->pbvh; if (pbvh != nullptr) { + blender::bke::pbvh::sharp_limit_set(pbvh, ss->sharp_angle_limit); + /* NOTE: It is possible that pointers to grids or other geometry data changed. Need to update * those pointers. */ const PBVHType pbvh_type = BKE_pbvh_type(pbvh); switch (pbvh_type) { case PBVH_FACES: { - pbvh::update_mesh_pointers(pbvh, BKE_object_get_original_mesh(ob)); + BKE_pbvh_update_mesh_pointers(pbvh, BKE_object_get_original_mesh(ob)); break; } case PBVH_GRIDS: { Object *object_eval = DEG_get_evaluated_object(depsgraph, ob); Mesh *mesh_eval = static_cast(object_eval->data); - if (SubdivCCG *subdiv_ccg = mesh_eval->runtime->subdiv_ccg.get()) { + SubdivCCG *subdiv_ccg = mesh_eval->runtime->subdiv_ccg.get(); + if (subdiv_ccg != nullptr) { BKE_sculpt_bvh_update_from_ccg(pbvh, subdiv_ccg); } break; @@ -2181,34 +2840,79 @@ PBVH *BKE_sculpt_object_pbvh_ensure(Depsgraph *depsgraph, Object *ob) } } + BKE_sculptsession_sync_attributes(ob, BKE_object_get_original_mesh(ob), false); BKE_pbvh_update_active_vcol(pbvh, BKE_object_get_original_mesh(ob)); - BKE_pbvh_pmap_set(pbvh, ob->sculpt->vert_to_face_map); + blender::bke::pbvh::set_pmap(pbvh, ob->sculpt->vert_to_face_map); return pbvh; } - ob->sculpt->islands_valid = false; + ss->islands_valid = false; - if (ob->sculpt->bm != nullptr) { + if (ss->bm != nullptr) { /* Sculpting on a BMesh (dynamic-topology) gets a special PBVH. */ - pbvh = build_pbvh_for_dynamic_topology(ob); + pbvh = ss->pbvh = build_pbvh_for_dynamic_topology(ob, false); } else { - Object *object_eval = DEG_get_evaluated_object(depsgraph, ob); - Mesh *mesh_eval = static_cast(object_eval->data); - if (mesh_eval->runtime->subdiv_ccg != nullptr) { - pbvh = build_pbvh_from_ccg(ob, mesh_eval->runtime->subdiv_ccg.get()); + /* Detect if we are loading from an undo memfile step. */ + Mesh *mesh_orig = BKE_object_get_original_mesh(ob); + bool is_dyntopo = (mesh_orig->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) && + ss->mode_type == OB_MODE_SCULPT; + + if (is_dyntopo) { + BMesh *bm = BKE_sculptsession_empty_bmesh_create(); + + BMeshFromMeshParams params = {0}; + params.calc_face_normal = true; + params.use_shapekey = true; + params.active_shapekey = ob->shapenr; + params.copy_temp_cdlayers = true; + + BM_mesh_bm_from_me(bm, mesh_orig, ¶ms); + BM_mesh_elem_table_ensure(bm, BM_VERT | BM_EDGE | BM_FACE); + + ss->bm = bm; + + BKE_sculpt_ensure_idmap(ob); + blender::ed::sculpt_paint::undo::ensure_bmlog(ob); + + pbvh = ss->pbvh = build_pbvh_for_dynamic_topology(ob, true); + + if (!CustomData_has_layer_named(&ss->bm->vdata, CD_PROP_FLOAT, ".sculpt_mask")) { + BM_data_layer_add_named(ss->bm, &ss->bm->vdata, CD_PROP_FLOAT, ".sculpt_mask"); + BKE_sculptsession_update_attr_refs(ob); + } + + BKE_sculpt_ensure_origco(ob); + BKE_sculpt_ensure_sculpt_layers(ob); + + BKE_sculpt_init_flags_valence(ob, pbvh, bm->totvert, true); + blender::bke::paint::load_all_original(ob); } - else if (ob->type == OB_MESH) { - Mesh *me_eval_deform = object_eval->runtime->mesh_deform_eval; - pbvh = build_pbvh_from_regular_mesh(ob, me_eval_deform); + else { + Object *object_eval = DEG_get_evaluated_object(depsgraph, ob); + Mesh *mesh_eval = static_cast(object_eval->data); + if (mesh_eval->runtime->subdiv_ccg != nullptr) { + pbvh = build_pbvh_from_ccg(ob, mesh_eval->runtime->subdiv_ccg.get()); + } + else if (ob->type == OB_MESH) { + Mesh *me_eval_deform = object_eval->runtime->mesh_deform_eval; + pbvh = build_pbvh_from_regular_mesh(ob, me_eval_deform); + } } } - BKE_pbvh_pmap_set(pbvh, ob->sculpt->vert_to_face_map); - ob->sculpt->pbvh = pbvh; + ss->pbvh = pbvh; + blender::bke::pbvh::set_pmap(pbvh, ss->vert_to_face_map); sculpt_attribute_update_refs(ob); + + /* Forcibly flag all boundaries for update. */ + sculpt_boundary_flags_ensure( + ob, pbvh, BKE_sculptsession_vertex_count(ss), ss->bm ? ss->bm->totedge : ss->totedges, true); + + sculpt_attribute_update_refs(ob); + return pbvh; } @@ -2238,6 +2942,12 @@ bool BKE_sculptsession_use_pbvh_draw(const Object *ob, const RegionView3D *rv3d) return false; } +#if 0 + if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { + return !(v3d && (v3d->shading.type > OB_SOLID)); + } +#endif + if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) { /* Regular mesh only draws from PBVH without modifiers and shape keys, or for * external engines that do not have access to the PBVH like Eevee does. */ @@ -2269,53 +2979,279 @@ void BKE_paint_face_set_overlay_color_get(const int face_set, const int seed, uc int BKE_sculptsession_vertex_count(const SculptSession *ss) { - if (ss->bm) { - return ss->bm->totvert; + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + return ss->totvert; + case PBVH_BMESH: + return BM_mesh_elem_count(ss->bm, BM_VERT); + case PBVH_GRIDS: + return BKE_pbvh_get_grid_num_verts(ss->pbvh); } - if (ss->subdiv_ccg) { - return ss->subdiv_ccg->grids.size() * BKE_subdiv_ccg_key_top_level(*ss->subdiv_ccg).grid_area; - } - return ss->totvert; + + return 0; } +static bool sculpt_attribute_stored_in_bmesh_builtin(const StringRef name) +{ + return BM_attribute_stored_in_bmesh_builtin(name); +} + +static bool sync_ignore_layer(CustomDataLayer *layer) +{ + int badmask = CD_MASK_ORIGINDEX | CD_MASK_ORIGSPACE | CD_MASK_MFACE; + + bool bad = sculpt_attribute_stored_in_bmesh_builtin(layer->name); + bad = bad || ((1 << layer->type) & badmask); + bad = bad || (layer->flag & (CD_FLAG_TEMPORARY | CD_FLAG_NOCOPY)); + + return bad; +} /** - * Returns pointer to a CustomData associated with a given domain, if - * one exists. If not nullptr is returned (this may happen with e.g. - * multires and #AttrDomain::Point). - */ -static CustomData *sculpt_get_cdata(Object *ob, AttrDomain domain) + Syncs customdata layers with internal bmesh, but ignores deleted layers. +*/ +static void get_synced_attributes(CustomData *src_data, + CustomData *dst_data, + Vector &r_new, + Vector &r_kill) +{ + for (int i : IndexRange(src_data->totlayer)) { + CustomDataLayer *src_layer = src_data->layers + i; + + if (sync_ignore_layer(src_layer)) { + continue; + } + + if (CustomData_get_named_layer_index( + dst_data, eCustomDataType(src_layer->type), src_layer->name) == -1) + { + r_new.append(*src_layer); + } + } + + for (int i : IndexRange(dst_data->totlayer)) { + CustomDataLayer *dst_layer = dst_data->layers + i; + + if (sync_ignore_layer(dst_layer)) { + continue; + } + + if (CustomData_get_named_layer_index( + src_data, eCustomDataType(dst_layer->type), dst_layer->name) == -1) + { + r_kill.append(*dst_layer); + } + } +} + +static bool sync_attribute_actives(CustomData *src_data, CustomData *dst_data) +{ + bool modified = false; + + bool donemap[CD_NUMTYPES] = {0}; + + for (int i : IndexRange(src_data->totlayer)) { + CustomDataLayer *src_layer = src_data->layers + i; + eCustomDataType type = eCustomDataType(src_layer->type); + + if (sync_ignore_layer(src_layer) || donemap[int(type)]) { + continue; + } + + /* Only do first layers of each type, active refs will be propagated to + * the other ones later. + */ + donemap[src_layer->type] = true; + + /* Find first layer of type. */ + int baseidx = CustomData_get_layer_index(dst_data, type); + + if (baseidx < 0) { + modified |= true; + continue; + } + + CustomDataLayer *dst_layer = dst_data->layers + baseidx; + + int idx = CustomData_get_named_layer_index(dst_data, type, src_layer[src_layer->active].name); + if (idx >= 0) { + modified |= idx - baseidx != dst_layer->active; + dst_layer->active = idx - baseidx; + } + else { + modified |= dst_layer->active != 0; + dst_layer->active = 0; + } + + idx = CustomData_get_named_layer_index(dst_data, type, src_layer[src_layer->active_rnd].name); + if (idx >= 0) { + modified |= idx - baseidx != dst_layer->active_rnd; + dst_layer->active_rnd = idx - baseidx; + } + else { + modified |= dst_layer->active_rnd != 0; + dst_layer->active_rnd = 0; + } + + idx = CustomData_get_named_layer_index(dst_data, type, src_layer[src_layer->active_mask].name); + if (idx >= 0) { + modified |= idx - baseidx != dst_layer->active_mask; + dst_layer->active_mask = idx - baseidx; + } + else { + modified |= dst_layer->active_mask != 0; + dst_layer->active_mask = 0; + } + + idx = CustomData_get_named_layer_index( + dst_data, type, src_layer[src_layer->active_clone].name); + if (idx >= 0) { + modified |= idx - baseidx != dst_layer->active_clone; + dst_layer->active_clone = idx - baseidx; + } + else { + modified |= dst_layer->active_clone != 0; + dst_layer->active_clone = 0; + } + } + + if (modified) { + CustomDataLayer *base_layer = dst_data->layers; + + for (int i = 0; i < dst_data->totlayer; i++) { + CustomDataLayer *dst_layer = dst_data->layers; + + if (dst_layer->type != base_layer->type) { + base_layer = dst_layer; + } + + dst_layer->active = base_layer->active; + dst_layer->active_clone = base_layer->active_clone; + dst_layer->active_mask = base_layer->active_mask; + dst_layer->active_rnd = base_layer->active_rnd; + } + } + + return modified; +} + +void BKE_sculptsession_sync_attributes(struct Object *ob, struct Mesh *me, bool load_to_mesh) { SculptSession *ss = ob->sculpt; - if (ss->bm) { - switch (domain) { - case AttrDomain::Point: - return &ss->bm->vdata; - case AttrDomain::Face: - return &ss->bm->pdata; - default: - BLI_assert_unreachable(); - return nullptr; + if (!ss) { + return; + } + else if (!ss->bm) { + if (!load_to_mesh) { + BKE_sculptsession_update_attr_refs(ob); + } + return; + } + + bool modified = false; + + BMesh *bm = ss->bm; + + CustomData *cdme[4] = {&me->vert_data, &me->edge_data, &me->corner_data, &me->face_data}; + CustomData *cdbm[4] = {&bm->vdata, &bm->edata, &bm->ldata, &bm->pdata}; + + if (!load_to_mesh) { + for (int i = 0; i < 4; i++) { + Vector new_layers, kill_layers; + + get_synced_attributes(cdme[i], cdbm[i], new_layers, kill_layers); + + for (CustomDataLayer &layer : kill_layers) { + BM_data_layer_free_named(bm, cdbm[i], layer.name); + modified = true; + } + + Vector new_bm_layers; + for (CustomDataLayer &layer : new_layers) { + new_bm_layers.append({layer.type, layer.name, layer.flag}); + } + + BM_data_layers_ensure(bm, cdbm[i], new_bm_layers.data(), new_bm_layers.size()); + + modified |= new_bm_layers.size() > 0; + modified |= sync_attribute_actives(cdme[i], cdbm[i]); } } else { - Mesh *mesh = BKE_object_get_original_mesh(ob); + for (int i = 0; i < 4; i++) { + Vector new_layers, kill_layers; - switch (domain) { - case AttrDomain::Point: - /* Cannot get vertex domain for multires grids. */ - if (ss->pbvh && BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { - return nullptr; - } + get_synced_attributes(cdbm[i], cdme[i], new_layers, kill_layers); - return &mesh->vert_data; - case AttrDomain::Face: - return &mesh->face_data; - default: - BLI_assert_unreachable(); - return nullptr; + int totelem; + switch (i) { + case 0: + totelem = me->verts_num; + break; + case 1: + totelem = me->edges_num; + break; + case 2: + totelem = me->corners_num; + break; + case 3: + totelem = me->faces_num; + break; + } + + for (CustomDataLayer &layer : new_layers) { + CustomData_add_layer_named( + cdme[i], eCustomDataType(layer.type), CD_CONSTRUCT, totelem, layer.name); + modified = true; + } + + for (CustomDataLayer &layer : kill_layers) { + CustomData_free_layer_named(cdme[i], layer.name, totelem); + modified = true; + } + + modified |= sync_attribute_actives(cdbm[i], cdme[i]); + } + + if (me->default_color_attribute && + !BKE_id_attributes_color_find(&me->id, me->active_color_attribute)) + { + MEM_SAFE_FREE(me->active_color_attribute); + } + if (me->default_color_attribute && + !BKE_id_attributes_color_find(&me->id, me->default_color_attribute)) + { + MEM_SAFE_FREE(me->default_color_attribute); } } + + if (modified) { + printf("%s: Attribute layout changed! %s\n", + __func__, + load_to_mesh ? "Loading to mesh" : "Loading from mesh"); + } + + if (!load_to_mesh) { + BKE_sculptsession_update_attr_refs(ob); + } +}; + +BMesh *BKE_sculptsession_empty_bmesh_create() +{ + BMAllocTemplate allocsize; + + allocsize.totvert = 2048 * 1; + allocsize.totface = 2048 * 16; + allocsize.totloop = 4196 * 16; + allocsize.totedge = 2048 * 16; + + BMeshCreateParams params = {0}; + + params.use_toolflags = false; + + BMesh *bm = BM_mesh_create(&allocsize, ¶ms); + + return bm; } static int sculpt_attr_elem_count_get(Object *ob, AttrDomain domain) @@ -2324,11 +3260,25 @@ static int sculpt_attr_elem_count_get(Object *ob, AttrDomain domain) switch (domain) { case AttrDomain::Point: - return BKE_sculptsession_vertex_count(ss); + /* Cannot rely on prescence of ss->pbvh. */ + + if (ss->bm) { + return ss->bm->totvert; + } + else if (ss->subdiv_ccg) { + CCGKey key = BKE_subdiv_ccg_key_top_level(*ss->subdiv_ccg); + return ss->subdiv_ccg->grids.size() * key.grid_area; + } + else { + Mesh *me = BKE_object_get_original_mesh(ob); + + return me->verts_num; + } break; case AttrDomain::Face: return ss->totfaces; - break; + case AttrDomain::Edge: + return ss->bm ? ss->bm->totedge : BKE_object_get_original_mesh(ob)->edges_num; default: BLI_assert_unreachable(); return 0; @@ -2342,10 +3292,9 @@ static bool sculpt_attribute_create(SculptSession *ss, const char *name, SculptAttribute *out, const SculptAttributeParams *params, - PBVHType pbvhtype, - bool flat_array_for_bmesh) + PBVHType pbvhtype) { - Mesh *mesh = BKE_object_get_original_mesh(ob); + Mesh *me = BKE_object_get_original_mesh(ob); bool simple_array = params->simple_array; bool permanent = params->permanent; @@ -2356,10 +3305,10 @@ static bool sculpt_attribute_create(SculptSession *ss, STRNCPY_UTF8(out->name, name); /* Force non-CustomData simple_array mode if not PBVH_FACES. */ - if (pbvhtype == PBVH_GRIDS || (pbvhtype == PBVH_BMESH && flat_array_for_bmesh)) { + if (pbvhtype == PBVH_GRIDS && domain == AttrDomain::Point) { if (permanent) { printf( - "%s: error: tried to make permanent customdata in multires or bmesh mode; will make " + "%s: error: tried to make permanent customdata in multires; will make " "local " "array " "instead.\n", @@ -2390,69 +3339,87 @@ static bool sculpt_attribute_create(SculptSession *ss, return true; } - out->simple_array = false; + switch (pbvhtype) { + out->simple_array = false; - if (BMesh *bm = ss->bm) { - CustomData *cdata = nullptr; - out->data_for_bmesh = true; + case PBVH_BMESH: { + CustomData *cdata = nullptr; + out->data_for_bmesh = true; - switch (domain) { - case AttrDomain::Point: - cdata = &bm->vdata; - break; - case AttrDomain::Face: - cdata = &bm->pdata; - break; - default: - out->used = false; - return false; + switch (domain) { + case AttrDomain::Point: + cdata = &ss->bm->vdata; + break; + case AttrDomain::Edge: + cdata = &ss->bm->edata; + break; + case AttrDomain::Face: + cdata = &ss->bm->pdata; + break; + default: + out->used = false; + return false; + } + + if (CustomData_get_named_layer_index(cdata, proptype, name) == -1) { + BM_data_layer_add_named(ss->bm, cdata, proptype, name); + } + int index = CustomData_get_named_layer_index(cdata, proptype, name); + + if (!permanent) { + cdata->layers[index].flag |= CD_FLAG_TEMPORARY | CD_FLAG_NOCOPY; + } + else { + /* Push attribute into the base mesh. */ + BKE_sculptsession_sync_attributes(ob, static_cast(ob->data), true); + } + + out->data = nullptr; + out->layer = cdata->layers + index; + out->bmesh_cd_offset = out->layer->offset; + out->elem_size = CustomData_sizeof(proptype); + break; } + case PBVH_GRIDS: + case PBVH_FACES: { + CustomData *cdata = nullptr; - BLI_assert(CustomData_get_named_layer_index(cdata, proptype, name) == -1); + switch (domain) { + case AttrDomain::Point: + cdata = &me->vert_data; + break; + case AttrDomain::Face: + cdata = &me->face_data; + break; + case AttrDomain::Edge: + cdata = &me->edge_data; + break; + default: + out->used = false; + return false; + } - BM_data_layer_add_named(bm, cdata, proptype, name); - int index = CustomData_get_named_layer_index(cdata, proptype, name); + if (CustomData_get_named_layer_index(cdata, proptype, name) == -1) { + CustomData_add_layer_named(cdata, proptype, CD_SET_DEFAULT, totelem, name); + } + int index = CustomData_get_named_layer_index(cdata, proptype, name); - if (!permanent) { - cdata->layers[index].flag |= CD_FLAG_TEMPORARY | CD_FLAG_NOCOPY; + if (!permanent) { + cdata->layers[index].flag |= CD_FLAG_TEMPORARY | CD_FLAG_NOCOPY; + } + + out->layer = cdata->layers + index; + out->data = out->layer->data; + out->data_for_bmesh = false; + out->bmesh_cd_offset = -1; + out->elem_size = CustomData_sizeof(proptype); + + break; } - - out->data = nullptr; - out->layer = cdata->layers + index; - out->bmesh_cd_offset = out->layer->offset; - out->elem_size = CustomData_sizeof(proptype); + default: + BLI_assert_unreachable(); + break; } - else { - CustomData *cdata = nullptr; - - switch (domain) { - case AttrDomain::Point: - cdata = &mesh->vert_data; - break; - case AttrDomain::Face: - cdata = &mesh->face_data; - break; - default: - out->used = false; - return false; - } - - BLI_assert(CustomData_get_named_layer_index(cdata, proptype, name) == -1); - - CustomData_add_layer_named(cdata, proptype, CD_SET_DEFAULT, totelem, name); - int index = CustomData_get_named_layer_index(cdata, proptype, name); - - if (!permanent) { - cdata->layers[index].flag |= CD_FLAG_TEMPORARY | CD_FLAG_NOCOPY; - } - - out->layer = cdata->layers + index; - out->data = out->layer->data; - out->data_for_bmesh = false; - out->bmesh_cd_offset = -1; - out->elem_size = CustomData_get_elem_size(out->layer); - } - /* GRIDS should have been handled as simple arrays. */ out->used = true; out->elem_num = totelem; @@ -2472,8 +3439,8 @@ static bool sculpt_attr_update(Object *ob, SculptAttribute *attr) } /* Check if we are a coerced simple array and shouldn't be. */ - bad |= attr->simple_array && !attr->params.simple_array && - !ELEM(BKE_pbvh_type(ss->pbvh), PBVH_GRIDS, PBVH_BMESH); + bad |= (attr->simple_array && !attr->params.simple_array) && + !(ss->pbvh && BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS && attr->domain == AttrDomain::Point); CustomData *cdata = sculpt_get_cdata(ob, attr->domain); if (cdata && !attr->simple_array) { @@ -2485,11 +3452,50 @@ static bool sculpt_attr_update(Object *ob, SculptAttribute *attr) if (!bad) { if (attr->data_for_bmesh) { attr->bmesh_cd_offset = cdata->layers[layer_index].offset; + attr->data = nullptr; } else { - attr->data = cdata->layers[layer_index].data; + attr->data = CustomData_get_layer_named_for_write( + cdata, attr->proptype, attr->name, elem_num); } } + + if (layer_index != -1) { + if (attr->params.nocopy) { + cdata->layers[layer_index].flag |= CD_FLAG_ELEM_NOCOPY; + } + else { + cdata->layers[layer_index].flag &= ~CD_FLAG_ELEM_NOCOPY; + } + + if (attr->params.nointerp) { + cdata->layers[layer_index].flag |= CD_FLAG_ELEM_NOINTERP; + } + else { + cdata->layers[layer_index].flag &= ~CD_FLAG_ELEM_NOINTERP; + } + + if (attr->params.permanent) { + cdata->layers[layer_index].flag &= ~(CD_FLAG_TEMPORARY | CD_FLAG_NOCOPY); + } + else { + cdata->layers[layer_index].flag |= CD_FLAG_TEMPORARY | CD_FLAG_NOCOPY; + } + } + } + + PBVHType pbvhtype; + if (ss->pbvh) { + pbvhtype = BKE_pbvh_type(ss->pbvh); + } + else if (ss->bm) { + pbvhtype = PBVH_BMESH; + } + else if (ss->subdiv_ccg) { + pbvhtype = PBVH_GRIDS; + } + else { + pbvhtype = PBVH_FACES; } if (bad) { @@ -2497,15 +3503,12 @@ static bool sculpt_attr_update(Object *ob, SculptAttribute *attr) MEM_SAFE_FREE(attr->data); } - sculpt_attribute_create(ss, - ob, - attr->domain, - attr->proptype, - attr->name, - attr, - &attr->params, - BKE_pbvh_type(ss->pbvh), - attr->data_for_bmesh); + if (pbvhtype != PBVH_GRIDS && (attr->simple_array && !attr->params.simple_array)) { + attr->simple_array = false; + } + + sculpt_attribute_create( + ss, ob, attr->domain, attr->proptype, attr->name, attr, &attr->params, pbvhtype); } return bad; @@ -2530,6 +3533,22 @@ static SculptAttribute *sculpt_get_cached_layer(SculptSession *ss, return nullptr; } +bool BKE_sculpt_attribute_exists(Object *ob, + AttrDomain domain, + eCustomDataType proptype, + const char *name) +{ + SculptSession *ss = ob->sculpt; + SculptAttribute *attr = sculpt_get_cached_layer(ss, domain, proptype, name); + + if (attr) { + return true; + } + + CustomData *cdata = sculpt_get_cdata(ob, domain); + return CustomData_get_named_layer_index(cdata, proptype, name) != -1; +} + static SculptAttribute *sculpt_alloc_attr(SculptSession *ss) { for (int i = 0; i < SCULPT_MAX_ATTRIBUTES; i++) { @@ -2563,36 +3582,42 @@ SculptAttribute *BKE_sculpt_attribute_get(Object *ob, return attr; } + if (!ss->pbvh || (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS && domain == AttrDomain::Point)) { + /* Don't pull from customdata for PBVH_GRIDS and vertex domain. + * Multires vertex attributes don't go through CustomData. + */ + return nullptr; + } + /* Does attribute exist in CustomData layout? */ CustomData *cdata = sculpt_get_cdata(ob, domain); if (cdata) { int index = CustomData_get_named_layer_index(cdata, proptype, name); if (index != -1) { - int totelem = 0; - - switch (domain) { - case AttrDomain::Point: - totelem = BKE_sculptsession_vertex_count(ss); - break; - case AttrDomain::Face: - totelem = ss->totfaces; - break; - default: - BLI_assert_unreachable(); - break; - } + int totelem = sculpt_attr_elem_count_get(ob, domain); attr = sculpt_alloc_attr(ss); + if (ss->pbvh && (BKE_pbvh_type(ss->pbvh) == PBVH_FACES || + (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS && + ELEM(domain, AttrDomain::Face, AttrDomain::Edge)))) + { + attr->data = CustomData_get_layer_named_for_write( + cdata, attr->proptype, attr->name, totelem); + } + + attr->params.nointerp = cdata->layers[index].flag & CD_FLAG_ELEM_NOINTERP; + attr->params.nocopy = cdata->layers[index].flag & CD_FLAG_ELEM_NOCOPY; + attr->params.permanent = !(cdata->layers[index].flag & CD_FLAG_TEMPORARY); attr->used = true; attr->domain = domain; attr->proptype = proptype; - attr->data = cdata->layers[index].data; attr->bmesh_cd_offset = cdata->layers[index].offset; attr->elem_num = totelem; attr->layer = cdata->layers + index; - attr->elem_size = CustomData_get_elem_size(attr->layer); + attr->elem_size = CustomData_sizeof(proptype); + attr->data_for_bmesh = ss->bm && attr->bmesh_cd_offset != -1; STRNCPY_UTF8(attr->name, name); return attr; @@ -2607,13 +3632,16 @@ static SculptAttribute *sculpt_attribute_ensure_ex(Object *ob, eCustomDataType proptype, const char *name, const SculptAttributeParams *params, - PBVHType pbvhtype, - bool flat_array_for_bmesh) + PBVHType pbvhtype) { SculptSession *ss = ob->sculpt; SculptAttribute *attr = BKE_sculpt_attribute_get(ob, domain, proptype, name); if (attr) { + attr->params.nocopy = params->nocopy; + attr->params.nointerp = params->nointerp; + attr->params.permanent = params->permanent; + sculpt_attr_update(ob, attr); /* Since "stroke_only" is not a CustomData flag we have @@ -2627,8 +3655,7 @@ static SculptAttribute *sculpt_attribute_ensure_ex(Object *ob, attr = sculpt_alloc_attr(ss); /* Create attribute. */ - sculpt_attribute_create( - ss, ob, domain, proptype, name, attr, params, pbvhtype, flat_array_for_bmesh); + sculpt_attribute_create(ss, ob, domain, proptype, name, attr, params, pbvhtype); sculpt_attribute_update_refs(ob); return attr; @@ -2643,20 +3670,47 @@ SculptAttribute *BKE_sculpt_attribute_ensure(Object *ob, SculptAttributeParams temp_params = *params; return sculpt_attribute_ensure_ex( - ob, domain, proptype, name, &temp_params, BKE_pbvh_type(ob->sculpt->pbvh), true); + ob, domain, proptype, name, &temp_params, BKE_pbvh_type(ob->sculpt->pbvh)); } static void sculptsession_bmesh_attr_update_internal(Object *ob) { - using namespace blender; SculptSession *ss = ob->sculpt; sculptsession_bmesh_add_layers(ob); + if (ss->bm_idmap) { + BM_idmap_check_attributes(ss->bm_idmap); + } if (ss->pbvh) { - bke::pbvh::update_bmesh_offsets(ss->pbvh, - ob->sculpt->attrs.dyntopo_node_id_vertex->bmesh_cd_offset, - ob->sculpt->attrs.dyntopo_node_id_face->bmesh_cd_offset); + int cd_face_area = ss->attrs.face_areas ? ss->attrs.face_areas->bmesh_cd_offset : -1; + int cd_boundary_flags = ss->attrs.boundary_flags ? ss->attrs.boundary_flags->bmesh_cd_offset : + -1; + int cd_edge_boundary = ss->attrs.edge_boundary_flags ? + ss->attrs.edge_boundary_flags->bmesh_cd_offset : + -1; + int cd_dyntopo_vert = ss->attrs.dyntopo_node_id_vertex ? + ss->attrs.dyntopo_node_id_vertex->bmesh_cd_offset : + -1; + int cd_dyntopo_face = ss->attrs.dyntopo_node_id_face ? + ss->attrs.dyntopo_node_id_face->bmesh_cd_offset : + -1; + int cd_flag = ss->attrs.flags ? ss->attrs.flags->bmesh_cd_offset : -1; + int cd_valence = ss->attrs.valence ? ss->attrs.valence->bmesh_cd_offset : -1; + + bke::pbvh::set_idmap(ss->pbvh, ss->bm_idmap); + bke::pbvh::update_offsets(ss->pbvh, + cd_dyntopo_vert, + cd_dyntopo_face, + cd_face_area, + cd_boundary_flags, + cd_edge_boundary, + cd_flag, + cd_valence, + ss->attrs.orig_co ? ss->attrs.orig_co->bmesh_cd_offset : -1, + ss->attrs.orig_no ? ss->attrs.orig_no->bmesh_cd_offset : -1, + ss->attrs.curvature_dir ? ss->attrs.curvature_dir->bmesh_cd_offset : + -1); } } @@ -2665,23 +3719,70 @@ static void sculptsession_bmesh_add_layers(Object *ob) SculptSession *ss = ob->sculpt; SculptAttributeParams params = {0}; - ss->attrs.dyntopo_node_id_vertex = sculpt_attribute_ensure_ex( - ob, - AttrDomain::Point, - CD_PROP_INT32, - SCULPT_ATTRIBUTE_NAME(dyntopo_node_id_vertex), - ¶ms, - PBVH_BMESH, - false); + params.nocopy = true; + params.nointerp = true; - ss->attrs.dyntopo_node_id_face = sculpt_attribute_ensure_ex( - ob, - AttrDomain::Face, - CD_PROP_INT32, - SCULPT_ATTRIBUTE_NAME(dyntopo_node_id_face), - ¶ms, - PBVH_BMESH, - false); + if (!ss->attrs.face_areas) { + SculptAttributeParams params = {0}; + ss->attrs.face_areas = sculpt_attribute_ensure_ex(ob, + AttrDomain::Face, + CD_PROP_FLOAT2, + SCULPT_ATTRIBUTE_NAME(face_areas), + ¶ms, + PBVH_BMESH); + } + + if (!ss->attrs.dyntopo_node_id_vertex) { + ss->attrs.dyntopo_node_id_vertex = sculpt_attribute_ensure_ex( + ob, + AttrDomain::Point, + CD_PROP_INT32, + SCULPT_ATTRIBUTE_NAME(dyntopo_node_id_vertex), + ¶ms, + PBVH_BMESH); + } + + if (!ss->attrs.dyntopo_node_id_face) { + ss->attrs.dyntopo_node_id_face = sculpt_attribute_ensure_ex( + ob, + AttrDomain::Face, + CD_PROP_INT32, + SCULPT_ATTRIBUTE_NAME(dyntopo_node_id_face), + ¶ms, + PBVH_BMESH); + } + + ss->cd_vert_node_offset = ss->attrs.dyntopo_node_id_vertex->bmesh_cd_offset; + ss->cd_face_node_offset = ss->attrs.dyntopo_node_id_face->bmesh_cd_offset; + ss->cd_face_areas = ss->attrs.face_areas ? ss->attrs.face_areas->bmesh_cd_offset : -1; +} + +template static void sculpt_clear_attribute_bmesh(BMesh *bm, SculptAttribute *attr) +{ + BMIter iter; + int itertype; + + switch (attr->domain) { + case AttrDomain::Point: + itertype = BM_VERTS_OF_MESH; + break; + case AttrDomain::Edge: + itertype = BM_EDGES_OF_MESH; + break; + case AttrDomain::Face: + itertype = BM_FACES_OF_MESH; + break; + default: + BLI_assert_unreachable(); + return; + } + + int size = CustomData_sizeof(attr->proptype); + + T *elem; + BM_ITER_MESH (elem, &iter, bm, itertype) { + memset(POINTER_OFFSET(elem->head.data, attr->bmesh_cd_offset), 0, size); + } } void BKE_sculpt_attributes_destroy_temporary_stroke(Object *ob) @@ -2692,16 +3793,100 @@ void BKE_sculpt_attributes_destroy_temporary_stroke(Object *ob) SculptAttribute *attr = ss->temp_attributes + i; if (attr->params.stroke_only) { + /* Don't free BMesh attribute as it is quite expensive; + * note that temporary attributes are still freed on + * exiting sculpt mode. + * + * Attributes allocated as simple arrays are fine however. + */ + + if (!attr->params.simple_array && ss->bm) { + /* Zero the attribute in an attempt to emulate the behavior of releasing it. */ + if (attr->domain == AttrDomain::Point) { + sculpt_clear_attribute_bmesh(ss->bm, attr); + } + else if (attr->domain == AttrDomain::Edge) { + sculpt_clear_attribute_bmesh(ss->bm, attr); + } + else if (attr->domain == AttrDomain::Face) { + sculpt_clear_attribute_bmesh(ss->bm, attr); + } + continue; + } + BKE_sculpt_attribute_destroy(ob, attr); } } } +static void update_bmesh_offsets(Object *ob) +{ + Mesh *me = BKE_object_get_original_mesh(ob); + SculptSession *ss = ob->sculpt; + + ss->cd_vert_node_offset = ss->attrs.dyntopo_node_id_vertex ? + ss->attrs.dyntopo_node_id_vertex->bmesh_cd_offset : + -1; + ss->cd_face_node_offset = ss->attrs.dyntopo_node_id_face ? + ss->attrs.dyntopo_node_id_face->bmesh_cd_offset : + -1; + ss->cd_origco_offset = ss->attrs.orig_co ? ss->attrs.orig_co->bmesh_cd_offset : -1; + ss->cd_origno_offset = ss->attrs.orig_no ? ss->attrs.orig_no->bmesh_cd_offset : -1; + ss->cd_origvcol_offset = ss->attrs.orig_color ? ss->attrs.orig_color->bmesh_cd_offset : -1; + + CustomDataLayer *layer = BKE_id_attribute_search_for_write( + &me->id, + BKE_id_attributes_active_color_name(&me->id), + CD_MASK_COLOR_ALL, + ATTR_DOMAIN_MASK_POINT | ATTR_DOMAIN_MASK_CORNER); + if (layer) { + AttrDomain domain = BKE_id_attribute_domain(&me->id, layer); + CustomData *cdata = sculpt_get_cdata(ob, domain); + + int layer_i = CustomData_get_named_layer_index( + cdata, eCustomDataType(layer->type), layer->name); + + ss->cd_vcol_offset = layer_i != -1 ? cdata->layers[layer_i].offset : -1; + } + else { + ss->cd_vcol_offset = -1; + } + + ss->cd_vert_mask_offset = CustomData_get_offset_named( + &ss->bm->vdata, CD_PROP_FLOAT, ".sculpt_mask"); + ss->cd_faceset_offset = CustomData_get_offset_named( + &ss->bm->pdata, CD_PROP_INT32, ".sculpt_face_set"); + ss->cd_face_areas = ss->attrs.face_areas ? ss->attrs.face_areas->bmesh_cd_offset : -1; + + int cd_boundary_flags = ss->attrs.boundary_flags ? ss->attrs.boundary_flags->bmesh_cd_offset : + -1; + int cd_edge_boundary = ss->attrs.edge_boundary_flags ? + ss->attrs.edge_boundary_flags->bmesh_cd_offset : + -1; + + if (ss->pbvh) { + bke::pbvh::update_offsets(ss->pbvh, + ss->cd_vert_node_offset, + ss->cd_face_node_offset, + ss->cd_face_areas, + cd_boundary_flags, + cd_edge_boundary, + ss->attrs.flags ? ss->attrs.flags->bmesh_cd_offset : -1, + ss->attrs.valence ? ss->attrs.valence->bmesh_cd_offset : -1, + ss->attrs.orig_co ? ss->attrs.orig_co->bmesh_cd_offset : -1, + ss->attrs.orig_no ? ss->attrs.orig_no->bmesh_cd_offset : -1, + ss->attrs.curvature_dir ? ss->attrs.curvature_dir->bmesh_cd_offset : + -1); + } +} + static void sculpt_attribute_update_refs(Object *ob) { SculptSession *ss = ob->sculpt; - /* Run twice, in case sculpt_attr_update had to recreate a layer and messed up #BMesh offsets. */ + /* Run twice, in case sculpt_attr_update had to recreate a layer and messed up #BMesh + * offsets. + */ for (int i = 0; i < 2; i++) { for (int j = 0; j < SCULPT_MAX_ATTRIBUTES; j++) { SculptAttribute *attr = ss->temp_attributes + j; @@ -2716,11 +3901,33 @@ static void sculpt_attribute_update_refs(Object *ob) } } - Mesh *mesh = BKE_object_get_original_mesh(ob); + Mesh *me = BKE_object_get_original_mesh(ob); if (ss->pbvh) { - BKE_pbvh_update_active_vcol(ss->pbvh, mesh); + BKE_pbvh_update_active_vcol(ss->pbvh, me); } + + if (ss->attrs.face_areas && ss->pbvh) { + BKE_pbvh_set_face_areas(ss->pbvh, (float *)ss->attrs.face_areas->data); + } + + if (ss->bm) { + update_bmesh_offsets(ob); + } + else if (ss->pbvh) { + if (ss->attrs.orig_co && ss->attrs.orig_no) { + const int verts_count = BKE_sculptsession_vertex_count(ss); + blender::bke::pbvh::set_original( + ob->sculpt->pbvh, + {static_cast(ss->attrs.orig_co->data), verts_count}, + {static_cast(ss->attrs.orig_no->data), verts_count}); + } + } +} + +void BKE_sculptsession_update_attr_refs(Object *ob) +{ + sculpt_attribute_update_refs(ob); } void BKE_sculpt_attribute_destroy_temporary_all(Object *ob) @@ -2736,8 +3943,114 @@ void BKE_sculpt_attribute_destroy_temporary_all(Object *ob) } } +SculptAttribute BKE_sculpt_find_attribute(Object *ob, const char *name) +{ + if (!name) { + SculptAttribute attr = {}; + return attr; + } + + SculptSession *ss = ob->sculpt; + + CustomData *cdatas[4]; + AttrDomain domains[4] = { + AttrDomain::Point, AttrDomain::Edge, AttrDomain::Corner, AttrDomain::Face}; + + if (ss->bm) { + cdatas[0] = &ss->bm->vdata; + cdatas[1] = &ss->bm->edata; + cdatas[2] = &ss->bm->ldata; + cdatas[3] = &ss->bm->pdata; + } + else { + Mesh *me = static_cast(ob->data); + + cdatas[0] = &me->vert_data; + cdatas[1] = &me->edge_data; + cdatas[2] = &me->corner_data; + cdatas[3] = &me->face_data; + } + + bool is_grids = ss->pbvh && BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS; + + for (int i = 0; i < 4; i++) { + AttrDomain domain = domains[i]; + if (domain == AttrDomain::Point && is_grids) { + for (int i = 0; i < ARRAY_SIZE(ss->temp_attributes); i++) { + SculptAttribute *attr = &ss->temp_attributes[i]; + + if (attr->used && attr->domain == AttrDomain::Point && STREQ(attr->name, name)) { + return *attr; + } + } + + continue; + } + + CustomData *data = cdatas[i]; + for (int j = 0; j < data->totlayer; j++) { + CustomDataLayer &layer = data->layers[j]; + + if (STREQ(layer.name, name) && (CD_TYPE_AS_MASK(layer.type) & CD_MASK_PROP_ALL)) { + SculptAttribute *attr = BKE_sculpt_attribute_get( + ob, domain, eCustomDataType(layer.type), name); + SculptAttribute ret; + + ret = *attr; + BKE_sculpt_attribute_release_ref(ob, attr); + + return ret; + } + } + } + + SculptAttribute unused = {}; + return unused; +} + +void BKE_sculpt_attribute_release_ref(Object *ob, SculptAttribute *attr) +{ + SculptSession *ss = ob->sculpt; + + BLI_assert(attr->used); + + /* Remove from convenience pointer struct. */ + SculptAttribute **ptrs = (SculptAttribute **)&ss->attrs; + int ptrs_num = sizeof(ss->attrs) / sizeof(void *); + + for (int i = 0; i < ptrs_num; i++) { + if (ptrs[i] == attr) { + ptrs[i] = nullptr; + } + } + + /* Remove from internal temp_attributes array. */ + for (int i = 0; i < SCULPT_MAX_ATTRIBUTES; i++) { + SculptAttribute *attr2 = ss->temp_attributes + i; + + if (STREQ(attr2->name, attr->name) && attr2->domain == attr->domain && + attr2->proptype == attr->proptype) + { + + attr2->used = false; + } + } + + /* Simple_array mode attributes are owned by SculptAttribute. */ + if (attr->simple_array) { + MEM_SAFE_FREE(attr->data); + } + + attr->data = nullptr; + attr->used = false; +} + bool BKE_sculpt_attribute_destroy(Object *ob, SculptAttribute *attr) { + if (!attr || !attr->used) { + return false; + } + SculptSession *ss = ob->sculpt; AttrDomain domain = attr->domain; @@ -2765,15 +4078,15 @@ bool BKE_sculpt_attribute_destroy(Object *ob, SculptAttribute *attr) } } - Mesh *mesh = BKE_object_get_original_mesh(ob); - + Mesh *me = BKE_object_get_original_mesh(ob); + ; if (attr->simple_array) { MEM_SAFE_FREE(attr->data); } else if (ss->bm) { - CustomData *cdata = attr->domain == AttrDomain::Point ? &ss->bm->vdata : &ss->bm->pdata; - - BM_data_layer_free_named(ss->bm, cdata, attr->name); + if (attr->data_for_bmesh) { + BM_data_layer_free_named(ss->bm, sculpt_get_cdata(ob, attr->domain), attr->name); + } } else { CustomData *cdata = nullptr; @@ -2781,11 +4094,15 @@ bool BKE_sculpt_attribute_destroy(Object *ob, SculptAttribute *attr) switch (domain) { case AttrDomain::Point: - cdata = ss->bm ? &ss->bm->vdata : &mesh->vert_data; + cdata = ss->bm ? &ss->bm->vdata : &me->vert_data; totelem = ss->totvert; break; + case AttrDomain::Edge: + cdata = ss->bm ? &ss->bm->edata : &me->edge_data; + totelem = BKE_object_get_original_mesh(ob)->edges_num; + break; case AttrDomain::Face: - cdata = ss->bm ? &ss->bm->pdata : &mesh->face_data; + cdata = ss->bm ? &ss->bm->pdata : &me->face_data; totelem = ss->totfaces; break; default: @@ -2796,16 +4113,789 @@ bool BKE_sculpt_attribute_destroy(Object *ob, SculptAttribute *attr) /* We may have been called after destroying ss->bm in which case attr->layer * might be invalid. */ - int layer_i = CustomData_get_named_layer_index(cdata, attr->proptype, attr->name); + int layer_i = CustomData_get_named_layer_index( + cdata, eCustomDataType(attr->proptype), attr->name); if (layer_i != 0) { CustomData_free_layer(cdata, attr->proptype, totelem, layer_i); } - - sculpt_attribute_update_refs(ob); } attr->data = nullptr; attr->used = false; + sculpt_attribute_update_refs(ob); + return true; } + +bool BKE_sculpt_has_persistent_base(SculptSession *ss) +{ + if (ss->bm) { + return CustomData_get_named_layer_index( + &ss->bm->vdata, CD_PROP_FLOAT3, SCULPT_ATTRIBUTE_NAME(persistent_co)) != -1; + } + else if (ss->vdata) { + return CustomData_get_named_layer_index( + ss->vdata, CD_PROP_FLOAT3, SCULPT_ATTRIBUTE_NAME(persistent_co)) != -1; + } + + /* Detect multires. */ + return ss->attrs.persistent_co; +} + +void BKE_sculpt_ensure_origco(struct Object *ob) +{ + SculptSession *ss = ob->sculpt; + SculptAttributeParams params = {}; + + if (!ss->attrs.orig_co) { + ss->attrs.orig_co = BKE_sculpt_attribute_ensure( + ob, AttrDomain::Point, CD_PROP_FLOAT3, SCULPT_ATTRIBUTE_NAME(orig_co), ¶ms); + } + if (!ss->attrs.orig_no) { + ss->attrs.orig_no = BKE_sculpt_attribute_ensure( + ob, AttrDomain::Point, CD_PROP_FLOAT3, SCULPT_ATTRIBUTE_NAME(orig_no), ¶ms); + } + + if (ss->pbvh) { + const int verts_count = BKE_sculptsession_vertex_count(ss); + blender::bke::pbvh::set_original( + ob->sculpt->pbvh, + {static_cast(ss->attrs.orig_co->data), verts_count}, + {static_cast(ss->attrs.orig_no->data), verts_count}); + } +} + +void BKE_sculpt_ensure_curvature_dir(struct Object *ob) +{ + SculptAttributeParams params = {}; + if (!ob->sculpt->attrs.curvature_dir) { + ob->sculpt->attrs.curvature_dir = BKE_sculpt_attribute_ensure( + ob, AttrDomain::Point, CD_PROP_FLOAT3, SCULPT_ATTRIBUTE_NAME(curvature_dir), ¶ms); + } +} + +void BKE_sculpt_ensure_origmask(struct Object *ob) +{ + SculptAttributeParams params = {}; + if (!ob->sculpt->attrs.orig_mask) { + ob->sculpt->attrs.orig_mask = BKE_sculpt_attribute_ensure( + ob, AttrDomain::Point, CD_PROP_FLOAT, SCULPT_ATTRIBUTE_NAME(orig_mask), ¶ms); + } +} +void BKE_sculpt_ensure_origcolor(struct Object *ob) +{ + SculptAttributeParams params = {}; + if (!ob->sculpt->attrs.orig_color) { + ob->sculpt->attrs.orig_color = BKE_sculpt_attribute_ensure( + ob, AttrDomain::Point, CD_PROP_COLOR, SCULPT_ATTRIBUTE_NAME(orig_color), ¶ms); + } +} + +void BKE_sculpt_ensure_origfset(struct Object *ob) +{ + SculptAttributeParams params = {}; + if (!ob->sculpt->attrs.orig_fsets) { + ob->sculpt->attrs.orig_fsets = BKE_sculpt_attribute_ensure( + ob, AttrDomain::Face, CD_PROP_INT32, SCULPT_ATTRIBUTE_NAME(orig_fsets), ¶ms); + } +} + +void BKE_sculpt_ensure_sculpt_layers(struct Object *ob) +{ + SculptAttributeParams params = {}; + params.nointerp = params.nocopy = true; + + if (!ob->sculpt->attrs.flags) { + ob->sculpt->attrs.flags = BKE_sculpt_attribute_ensure( + ob, AttrDomain::Point, CD_PROP_INT8, SCULPT_ATTRIBUTE_NAME(flags), ¶ms); + } + if (!ob->sculpt->attrs.valence) { + ob->sculpt->attrs.valence = BKE_sculpt_attribute_ensure( + ob, AttrDomain::Point, CD_PROP_INT32, SCULPT_ATTRIBUTE_NAME(valence), ¶ms); + } + if (!ob->sculpt->attrs.stroke_id) { + ob->sculpt->attrs.stroke_id = BKE_sculpt_attribute_ensure( + ob, AttrDomain::Point, CD_PROP_INT32, SCULPT_ATTRIBUTE_NAME(stroke_id), ¶ms); + } + + if (ob->sculpt->pbvh) { + blender::bke::pbvh::set_flags_valence(ob->sculpt->pbvh, + static_cast(ob->sculpt->attrs.flags->data), + static_cast(ob->sculpt->attrs.valence->data)); + } +} + +void BKE_sculpt_set_bmesh(Object *ob, BMesh *bm, bool free_existing) +{ + SculptSession *ss = ob->sculpt; + + if (bm == ss->bm) { + return; + } + + /* Destroy existing idmap. */ + if (ss->bm_idmap) { + BM_idmap_destroy(ss->bm_idmap); + ss->bm_idmap = nullptr; + } + + if (!ss->bm) { + ss->bm = bm; + return; + } + + /* Free existing bmesh. */ + if (ss->bm && free_existing) { + BM_mesh_free(ss->bm); + ss->bm = nullptr; + } + + /* Destroy toolflags if they exist (will reallocate the bmesh). */ + BM_mesh_toolflags_set(bm, false); + + /* Ensure element indices & lookup tables are up to date. */ + bm->elem_index_dirty = BM_VERT | BM_EDGE | BM_FACE; + bm->elem_table_dirty = BM_VERT | BM_EDGE | BM_FACE; + BM_mesh_elem_table_ensure(bm, BM_VERT | BM_EDGE | BM_FACE); + BM_mesh_elem_index_ensure(bm, BM_VERT | BM_EDGE | BM_FACE); + + /* Set new bmesh. */ + ss->bm = bm; + + /* Invalidate any existing bmesh attributes. */ + SculptAttribute **attrs = reinterpret_cast(&ss->attrs); + int attrs_num = int(sizeof(ss->attrs) / sizeof(void *)); + for (int i = 0; i < attrs_num; i++) { + if (attrs[i] && attrs[i]->data_for_bmesh) { + attrs[i]->used = false; + attrs[i] = nullptr; + } + } + + /* Check for any stray attributes in the pool that weren't stored in ss->attrs. */ + for (int i = 0; i < SCULPT_MAX_ATTRIBUTES; i++) { + SculptAttribute *attr = ss->temp_attributes + i; + if (attr->used && attr->data_for_bmesh) { + attr->used = false; + } + } + + if (!ss->pbvh) { + /* Do nothing, no pbvh to rebuild. */ + return; + } + + /* Destroy and rebuild pbvh */ + bke::pbvh::free(ss->pbvh); + ss->pbvh = nullptr; + + ss->pbvh = build_pbvh_for_dynamic_topology(ob, true); +} + +namespace blender::bke::paint { +bool get_original_vertex(SculptSession *ss, + PBVHVertRef vertex, + float **r_co, + float **r_no, + float **r_color, + float **r_mask) +{ + bool retval = false; + + if (sculpt::stroke_id_test(ss, vertex, STROKEID_USER_ORIGINAL)) { + if (ss->attrs.orig_co) { + const float *co = nullptr, *no = nullptr; + + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_BMESH: { + BMVert *v = reinterpret_cast(vertex.i); + co = v->co; + no = v->no; + break; + } + case PBVH_FACES: + if (ss->shapekey_active || ss->deform_modifiers_active) { + co = BKE_pbvh_get_vert_positions(ss->pbvh)[vertex.i]; + } + else { + co = ss->vert_positions[vertex.i]; + } + + no = BKE_pbvh_get_vert_normals(ss->pbvh)[vertex.i]; + break; + case PBVH_GRIDS: + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; + CCGElem *elem = BKE_pbvh_get_grids(ss->pbvh)[grid_index]; + + co = CCG_elem_co(key, CCG_elem_offset(key, elem, vertex_index)); + no = CCG_elem_no(key, CCG_elem_offset(key, elem, vertex_index)); + + break; + } + + copy_v3_v3(vertex_attr_ptr(vertex, ss->attrs.orig_co), co); + copy_v3_v3(vertex_attr_ptr(vertex, ss->attrs.orig_no), no); + } + + bool have_colors = BKE_pbvh_type(ss->pbvh) != PBVH_GRIDS && + ((ss->bm && ss->cd_vcol_offset != -1) || ss->vcol || ss->mcol); + + if (ss->attrs.orig_color && have_colors) { + BKE_pbvh_vertex_color_get( + ss->pbvh, vertex, vertex_attr_ptr(vertex, ss->attrs.orig_color)); + } + + if (ss->attrs.orig_mask) { + const float *mask = nullptr; + + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + mask = ss->vmask ? &ss->vmask[vertex.i] : nullptr; + break; + case PBVH_BMESH: { + BMVert *v; + int cd_mask = ss->cd_vert_mask_offset; + + v = (BMVert *)vertex.i; + mask = cd_mask != -1 ? static_cast(BM_ELEM_CD_GET_VOID_P(v, cd_mask)) : nullptr; + break; + } + case PBVH_GRIDS: { + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + + if (key->mask_offset == -1) { + mask = nullptr; + } + else { + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; + CCGElem *elem = BKE_pbvh_get_grids(ss->pbvh)[grid_index]; + mask = CCG_elem_mask(key, CCG_elem_offset(key, elem, vertex_index)); + } + break; + } + } + + if (mask) { + vertex_attr_set(vertex, ss->attrs.orig_mask, *mask); + } + } + + retval = true; + } + + if (r_co && ss->attrs.orig_co) { + *r_co = vertex_attr_ptr(vertex, ss->attrs.orig_co); + } + if (r_no && ss->attrs.orig_no) { + *r_no = vertex_attr_ptr(vertex, ss->attrs.orig_no); + } + if (r_color && ss->attrs.orig_color) { + *r_color = vertex_attr_ptr(vertex, ss->attrs.orig_color); + } + if (r_mask && ss->attrs.orig_mask) { + *r_mask = vertex_attr_ptr(vertex, ss->attrs.orig_mask); + } + + return retval; +} + +void load_all_original(Object *ob) +{ + SculptSession *ss = ob->sculpt; + + int verts_count = BKE_sculptsession_vertex_count(ss); + for (int i : IndexRange(verts_count)) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + blender::bke::sculpt::stroke_id_clear(ss, vertex, STROKEID_USER_ORIGINAL); + get_original_vertex(ss, vertex, nullptr, nullptr, nullptr, nullptr); + } +} + +} // namespace blender::bke::paint + +namespace blender::bke::sculpt { +void sculpt_vert_boundary_ensure(Object *object) +{ + using namespace blender; + SculptSession *ss = object->sculpt; + + /* PBVH_BMESH now handles boundaries itself. */ + if (ss->bm || !ss->vertex_info.boundary.is_empty()) { + if (!ss->bm && ss->pbvh) { + blender::bke::pbvh::set_vert_boundary_map(ss->pbvh, &ss->vertex_info.boundary); + } + + return; + } + + Mesh *base_mesh = BKE_mesh_from_object(object); + const blender::Span edges = base_mesh->edges(); + const OffsetIndices polys = base_mesh->faces(); + const Span corner_edges = base_mesh->corner_edges(); + + ss->vertex_info.boundary.resize(base_mesh->verts_num); + int *adjacent_faces_edge_count = static_cast( + MEM_calloc_arrayN(base_mesh->edges_num, sizeof(int), "Adjacent face edge count")); + + for (const int i : polys.index_range()) { + for (const int edge : corner_edges.slice(polys[i])) { + adjacent_faces_edge_count[edge]++; + } + } + + for (const int e : edges.index_range()) { + if (adjacent_faces_edge_count[e] < 2) { + const int2 &edge = edges[e]; + BLI_BITMAP_SET(ss->vertex_info.boundary, edge[0], true); + BLI_BITMAP_SET(ss->vertex_info.boundary, edge[1], true); + } + } + + if (ss->pbvh) { + blender::bke::pbvh::set_vert_boundary_map(ss->pbvh, &ss->vertex_info.boundary); + } + + MEM_freeN(adjacent_faces_edge_count); +} + +float calc_uv_snap_limit(BMLoop *l, int cd_uv) +{ + BMVert *v = l->v; + + float avg_len = 0.0f; + int avg_tot = 0; + + BMEdge *e = v->e; + do { + BMLoop *l2 = e->l; + + if (!l2) { + continue; + } + + do { + float *uv1 = BM_ELEM_CD_PTR(l2, cd_uv); + float *uv2 = BM_ELEM_CD_PTR(l2->next, cd_uv); + + avg_len += len_v2v2(uv1, uv2); + avg_tot++; + } while ((l2 = l2->radial_next) != e->l); + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + if (avg_tot > 0) { + /* 1/10th of average uv edge length. */ + return (avg_len / avg_tot) * 0.1f; + } + else { + return 0.005f; + } +} + +/* Angle test. loop_is_corner calls this if the chart count test fails. */ +static bool loop_is_corner_angle(BMLoop *l, + int cd_offset, + const float limit, + const float angle_limit, + const CustomData * /*ldata*/) +{ + BMVert *v = l->v; + BMEdge *e = v->e; + + float2 uv_value = *BM_ELEM_CD_PTR(l, cd_offset); + + BMLoop *outer1 = nullptr, *outer2 = nullptr; + float2 outer1_value; + +#ifdef TEST_UV_CORNER_CALC + int cd_pin = -1, cd_sel = -1; + + bool test_mode = false; + // test_mode = l->v->head.hflag & BM_ELEM_SELECT; + + if (ldata && test_mode) { + char name[512]; + for (int i = 0; i < ldata->totlayer; i++) { + CustomDataLayer *layer = ldata->layers + i; + if (layer->offset == cd_offset) { + sprintf(name, ".pn.%s", layer->name); + cd_pin = CustomData_get_offset_named(ldata, CD_PROP_BOOL, name); + + sprintf(name, ".vs.%s", layer->name); + cd_sel = CustomData_get_offset_named(ldata, CD_PROP_BOOL, name); + } + } + + if (cd_sel != -1) { + test_mode = BM_ELEM_CD_GET_BOOL(l, cd_sel); + } + } + + if (test_mode) { + printf("%s: start\n", __func__); + } +#endif + + do { + BMLoop *l2 = e->l; + if (!l2) { + continue; + } + + do { + BMLoop *uv_l2 = l2->v == v ? l2 : l2->next; + float2 uv_value2 = *BM_ELEM_CD_PTR(uv_l2, cd_offset); + + BMLoop *other_uv_l2 = l2->v == v ? l2->next : l2; + float2 other_uv_value2 = *BM_ELEM_CD_PTR(other_uv_l2, cd_offset); + + if (math::distance_squared(uv_value, uv_value2) > limit * limit) { + continue; + } + + bool outer = l2 == l2->radial_next; + + BMLoop *l3 = l2->radial_next; + while (l3 != l2) { + BMLoop *other_uv_l3 = l3->v == v ? l3->next : l3; + float2 other_uv_value3 = *BM_ELEM_CD_PTR(other_uv_l3, cd_offset); + + if (math::distance_squared(other_uv_value2, other_uv_value3) > limit * limit) { +#ifdef TEST_UV_CORNER_CALC + if (test_mode && cd_pin != -1) { + // BM_ELEM_CD_SET_BOOL(other_uv_l2, cd_pin, true); + // BM_ELEM_CD_SET_BOOL(other_uv_l3, cd_pin, true); + } +#endif + + BMLoop *uv_l3 = l3->v == v ? l3 : l3->next; + float2 uv_value3 = *BM_ELEM_CD_PTR(uv_l3, cd_offset); + + /* other_uv_value might be valid for one of the two arms, check. */ + if (math::distance_squared(uv_value, uv_value3) <= limit * limit) { +#ifdef TEST_UV_CORNER_CALC + if (test_mode) { + printf("%s: case 1\n", __func__); + } +#endif + + outer1 = other_uv_l2; + outer2 = other_uv_l3; + goto outer_break; + } + + outer = true; + break; + } + + l3 = l3->radial_next; + } + + if (outer) { +#ifdef TEST_UV_CORNER_CALC + if (test_mode && cd_pin != -1) { + // BM_ELEM_CD_SET_BOOL(other_uv_l2, cd_pin, true); + } +#endif + + if (!outer1) { + outer1 = other_uv_l2; + outer1_value = other_uv_value2; + } + else if (other_uv_l2 != outer2 && + math::distance_squared(outer1_value, other_uv_value2) > limit * limit) + { + outer2 = other_uv_l2; + +#ifdef TEST_UV_CORNER_CALC + if (test_mode) { + printf("%s: case 2\n", __func__); + } +#endif + goto outer_break; + } + } + } while ((l2 = l2->radial_next) != e->l); + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + +outer_break: + +#ifdef TEST_UV_CORNER_CALC + if (test_mode) { + printf("%s: l: %p, l->v: %p, outer1: %p, outer2: %p\n", __func__, l, l->v, outer1, outer2); + } +#endif + + if (!outer1 || !outer2) { + return false; + } + +#ifdef TEST_UV_CORNER_CALC + if (test_mode && cd_pin != -1) { + // BM_ELEM_CD_SET_BOOL(outer1, cd_pin, true); + // BM_ELEM_CD_SET_BOOL(outer2, cd_pin, true); + } +#endif + + float2 t1 = *BM_ELEM_CD_PTR(outer1, cd_offset) - uv_value; + float2 t2 = *BM_ELEM_CD_PTR(outer2, cd_offset) - uv_value; + + normalize_v2(t1); + normalize_v2(t2); + + if (dot_v2v2(t1, t2) < 0.0f) { + negate_v2(t2); + } + + float angle = math::safe_acos(dot_v2v2(t1, t2)); + +#ifdef TEST_UV_CORNER_CALC + if (test_mode) { + printf("%s: angle: %.5f\n", __func__, angle); + } +#endif + + bool ret = angle > angle_limit; + +#ifdef TEST_UV_CORNER_CALC + if (ret) { + // l->v->head.hflag |= BM_ELEM_SELECT; + } +#endif + + return ret; +} + +bool loop_is_corner(BMLoop *l, int cd_uv, float limit, const CustomData *ldata) +{ + BMVert *v = l->v; + + float2 value = *BM_ELEM_CD_PTR(l, cd_uv); + + Vector ls; + Vector keys; + + BMEdge *e = v->e; + do { + BMLoop *l2 = e->l; + + if (!l2) { + continue; + } + + do { + BMLoop *l3 = l2->v == v ? l2 : l2->next; + if (!ls.contains(l3)) { + ls.append(l3); + } + } while ((l2 = l2->radial_next) != e->l); + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + const float scale = 1.0f / limit; + + for (BMLoop *l2 : ls) { + float2 value2 = *BM_ELEM_CD_PTR(l2, cd_uv); + float2 dv = value2 - value; + + double f = dv[0] * dv[0] + dv[1] * dv[1]; + int key = int(f * scale); + if (!keys.contains(key)) { + keys.append(key); + } + } + + bool ret = keys.size() > 2; + + if (!ret) { + float angle_limit = 60.0f / 180.0f * M_PI; + + return loop_is_corner_angle(l, cd_uv, limit, angle_limit, ldata); + } + +#ifdef TEST_UV_CORNER_CALC + if (ret) { + // l->v->head.hflag |= BM_ELEM_SELECT; + } +#endif + + return ret; +} + +namespace detail { +static void corner_interp(CustomDataLayer * /*layer*/, + BMVert *v, + BMLoop *l, + Span loops, + Span ws, + int cd_offset, + float factor, + float2 &new_value) +{ + float *ws2 = (float *)BLI_array_alloca(ws2, loops.size() + 1); + float2 sum = {}; + float totsum = 0.0f; + + float2 value = *BM_ELEM_CD_PTR(l, cd_offset); + + float limit, limit_sqr; + + limit = calc_uv_snap_limit(l, cd_offset); + limit_sqr = limit * limit; + + /* Sum over uv verts surrounding l connected to the same chart. */ + for (int i : loops.index_range()) { + BMLoop *l2 = loops[i]; + BMLoop *l3; + + /* Find UV in l2's face that's owned by v. */ + if (l2->v == v) { + l3 = l2; + } + else if (l2->next->v == v) { + l3 = l2->next; + } + else { + l3 = l2->prev; + } + + float2 value3 = *BM_ELEM_CD_PTR(l3, cd_offset); + + if (math::distance_squared(value, value3) <= limit_sqr) { + float2 value2 = *BM_ELEM_CD_PTR(l2, cd_offset); + sum += value2 * ws[i]; + totsum += ws[i]; + } + } + + if (totsum == 0.0f) { + return; + } + + sum /= totsum; + new_value = value + (sum - value) * factor; +} + +/* Interpolates loops surrounding a vertex, splitting any UV map by + * island as appropriate and enforcing proper boundary conditions. + */ +static void interp_face_corners_intern(PBVH * /*pbvh*/, + BMVert *v, + Span loops, + Span ws, + float factor, + int cd_vert_boundary, + Span ls, + CustomDataLayer *layer) +{ + Vector corners; + Array new_values(ls.size()); + + /* Build (semantic) corner tags. */ + for (BMLoop *l : ls) { + /* Do not calculate the corner state here, use stored corner flag. + * + * The corner would normally be calculated like so: + * corner = loop_is_corner(l, layer->offset); + */ + bool corner = BM_ELEM_CD_GET_INT(v, cd_vert_boundary) & SCULPT_CORNER_UV; + + corners.append(corner); + } + + /* Interpolate loops. */ + for (int i : ls.index_range()) { + BMLoop *l = ls[i]; + + if (!corners[i]) { + corner_interp(layer, v, l, loops, ws, layer->offset, factor, new_values[i]); + } + } + + for (int i : ls.index_range()) { + if (!corners[i]) { + *BM_ELEM_CD_PTR(ls[i], layer->offset) = new_values[i]; + } + } +} +} // namespace detail + +void interp_face_corners(PBVH *pbvh, + PBVHVertRef vertex, + Span loops, + Span ws, + float factor, + int cd_vert_boundary) +{ + if (BKE_pbvh_type(pbvh) != PBVH_BMESH) { + return; /* Only for PBVH_BMESH. */ + } + + BMesh *bm = BKE_pbvh_get_bmesh(pbvh); + BMVert *v = reinterpret_cast(vertex.i); + BMEdge *e = v->e; + BMLoop *l = e->l; + CustomData *cdata = &bm->ldata; + + Vector ls; + + /* Tag loops around vertex. */ + do { + l = e->l; + + if (!l) { + continue; + } + + do { + BMLoop *l2 = l->v == v ? l : l->next; + BM_elem_flag_enable(l2, BM_ELEM_TAG); + } while ((l = l->radial_next) != e->l); + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + /* Build loop list. */ + do { + l = e->l; + + if (!l) { + continue; + } + + do { + BMLoop *l2 = l->v == v ? l : l->next; + if (BM_elem_flag_test(l2, BM_ELEM_TAG)) { + BM_elem_flag_disable(l2, BM_ELEM_TAG); + ls.append(l2); + } + } while ((l = l->radial_next) != e->l); + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + Vector layers; + for (int layer_i : IndexRange(cdata->totlayer)) { + CustomDataLayer *layer = cdata->layers + layer_i; + + if (layer->type != CD_PROP_FLOAT2 || + (layer->flag & (CD_FLAG_ELEM_NOINTERP | CD_FLAG_TEMPORARY))) + { + continue; + } + + layers.append(layer); + } + + /* Interpolate. */ + if (layers.size() > 0 && loops.size() > 1) { + // VertLoopSnapper corner_snap = {Span(ls), Span(layers)}; + + for (CustomDataLayer *layer : layers) { + detail::interp_face_corners_intern(pbvh, v, loops, ws, factor, cd_vert_boundary, ls, layer); + } + + /* Snap. */ + // corner_snap.snap(); + } +} +} // namespace blender::bke::sculpt diff --git a/source/blender/blenkernel/intern/pbvh.cc b/source/blender/blenkernel/intern/pbvh.cc index 2a10f434558..e8bc049bdd6 100644 --- a/source/blender/blenkernel/intern/pbvh.cc +++ b/source/blender/blenkernel/intern/pbvh.cc @@ -8,19 +8,33 @@ #include "MEM_guardedalloc.h" -#include +#include "BLI_utildefines.h" +#include "BLI_alloca.h" #include "BLI_array_utils.hh" #include "BLI_bit_span_ops.hh" #include "BLI_bitmap.h" #include "BLI_bounds.hh" +#include "BLI_bounds_types.hh" #include "BLI_enumerable_thread_specific.hh" +#include "BLI_ghash.h" +#include "BLI_listbase.h" #include "BLI_math_geom.h" #include "BLI_math_matrix.h" #include "BLI_math_vector.h" #include "BLI_math_vector.hh" +#include "BLI_offset_indices.hh" #include "BLI_rand.h" +#include "BLI_string.h" #include "BLI_task.h" +#include "BLI_timeit.hh" + +#include "BLI_index_range.hh" +#include "BLI_map.hh" +#include "BLI_math_vector.hh" +#include "BLI_math_vector_types.hh" +#include "BLI_set.hh" +#include "BLI_span.hh" #include "BLI_task.hh" #include "BLI_time.h" #include "BLI_timeit.hh" @@ -28,14 +42,23 @@ #include "BLI_vector.hh" #include "BLI_vector_set.hh" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + #include "BKE_attribute.hh" #include "BKE_ccg.h" +#include "BKE_main.hh" #include "BKE_mesh.hh" #include "BKE_mesh_mapping.hh" #include "BKE_paint.hh" #include "BKE_pbvh_api.hh" #include "BKE_subdiv_ccg.hh" +#include "DEG_depsgraph_query.hh" + #include "DRW_pbvh.hh" #include "bmesh.hh" @@ -44,16 +67,23 @@ #include "pbvh_intern.hh" -using blender::BitGroupVector; -using blender::Bounds; -using blender::float3; -using blender::MutableSpan; -using blender::Span; -using blender::Vector; +#include +#include + using blender::bke::AttrDomain; +using blender::bke::dyntopo::DyntopoSet; +using namespace blender; +using namespace blender::bke; +using blender::BitGroupVector; #define LEAF_LIMIT 10000 +/** Create invalid bounds for use with #math::min_max. */ +static Bounds negative_bounds() +{ + return {float3(std::numeric_limits::max()), float3(std::numeric_limits::lowest())}; +} + /* Uncomment to test if triangles of the same face are * properly clustered into single nodes. */ @@ -65,81 +95,194 @@ using blender::bke::AttrDomain; // #define PERFCNTRS #define STACK_FIXED_DEPTH 100 -struct PBVHStack { +typedef struct PBVHStack { PBVHNode *node; bool revisiting; -}; +} PBVHStack; -struct PBVHIter { +typedef struct PBVHIter { PBVH *pbvh; blender::FunctionRef scb; PBVHStack *stack; int stacksize; - PBVHStack stackfixed[STACK_FIXED_DEPTH]; + PBVHStack stackfixed[PBVH_STACK_FIXED_DEPTH]; int stackspace; -}; +} PBVHIter; -/** Create invalid bounds for use with #math::min_max. */ -static Bounds negative_bounds() +void BB_zero(BB *bb) { - return {float3(std::numeric_limits::max()), float3(std::numeric_limits::lowest())}; + bb->bmin[0] = bb->bmin[1] = bb->bmin[2] = 0.0f; + bb->bmax[0] = bb->bmax[1] = bb->bmax[2] = 0.0f; } -namespace blender::bke::pbvh { - -void update_node_bounds_mesh(const Span positions, PBVHNode &node) +void BB_reset(BB *bb) { - Bounds bounds = negative_bounds(); - for (const int vert : node.vert_indices) { - math::min_max(positions[vert], bounds.min, bounds.max); - } - node.vb = bounds; + bb->bmin[0] = bb->bmin[1] = bb->bmin[2] = FLT_MAX; + bb->bmax[0] = bb->bmax[1] = bb->bmax[2] = -FLT_MAX; } -void update_node_bounds_grids(const CCGKey &key, const Span grids, PBVHNode &node) +void BB_intersect(BB *r_out, BB *a, BB *b) { - Bounds bounds = negative_bounds(); - for (const int grid : node.prim_indices) { - for (const int i : IndexRange(key.grid_area)) { - math::min_max(float3(CCG_elem_offset_co(&key, grids[grid], i)), bounds.min, bounds.max); + for (int i = 0; i < 3; i++) { + r_out->bmin[i] = max_ff(a->bmin[i], b->bmin[i]); + r_out->bmax[i] = min_ff(a->bmax[i], b->bmax[i]); + + if (r_out->bmax[i] < r_out->bmin[i]) { + r_out->bmax[i] = r_out->bmin[i] = 0.0f; } } - node.vb = bounds; } -void update_node_bounds_bmesh(PBVHNode &node) +float BB_volume(const BB *bb) { - Bounds bounds = negative_bounds(); - for (const BMVert *vert : node.bm_unique_verts) { - math::min_max(float3(vert->co), bounds.min, bounds.max); + float dx = bb->bmax[0] - bb->bmin[0]; + float dy = bb->bmax[1] - bb->bmin[1]; + float dz = bb->bmax[2] - bb->bmin[2]; + + return dx * dy * dz; +} + +/* Expand the bounding box to include a new coordinate */ +void BB_expand(BB *bb, const float co[3]) +{ + for (int i = 0; i < 3; i++) { + bb->bmin[i] = min_ff(bb->bmin[i], co[i]); + bb->bmax[i] = max_ff(bb->bmax[i], co[i]); } - for (const BMVert *vert : node.bm_other_verts) { - math::min_max(float3(vert->co), bounds.min, bounds.max); +} + +void BB_expand_with_bb(BB *bb, const BB *bb2) +{ + for (int i = 0; i < 3; i++) { + bb->bmin[i] = min_ff(bb->bmin[i], bb2->bmin[i]); + bb->bmax[i] = max_ff(bb->bmax[i], bb2->bmax[i]); + } +} + +int BB_widest_axis(const BB *bb) +{ + float dim[3]; + + for (int i = 0; i < 3; i++) { + dim[i] = bb->bmax[i] - bb->bmin[i]; + } + + if (dim[0] > dim[1]) { + if (dim[0] > dim[2]) { + return 0; + } + + return 2; + } + + if (dim[1] > dim[2]) { + return 1; + } + + return 2; +} + +void BBC_update_centroid(BBC *bbc) +{ + for (int i = 0; i < 3; i++) { + bbc->bcentroid[i] = (bbc->bmin[i] + bbc->bmax[i]) * 0.5f; } - node.vb = bounds; } /* Not recursive */ -static void update_node_vb(PBVH *pbvh, PBVHNode *node) +static void update_node_vb(PBVH *pbvh, PBVHNode *node, int updateflag) { + auto not_leaf_or_has_faces = [&](PBVHNode *node) { + if (!(node->flag & PBVH_Leaf)) { + return true; + } + + return bool(node->bm_faces ? node->bm_faces->size() : node->prim_indices.size()); + }; + + if (!(updateflag & (PBVH_UpdateBB | PBVH_UpdateOriginalBB))) { + return; + } + + /* cannot clear flag here, causes leaky pbvh */ + // node->flag &= ~(updateflag & (PBVH_UpdateBB | PBVH_UpdateOriginalBB)); + + Bounds vb = {float3(FLT_MAX, FLT_MAX, FLT_MAX), float3(-FLT_MAX, -FLT_MAX, -FLT_MAX)}; + Bounds orig_vb = {float3(FLT_MAX, FLT_MAX, FLT_MAX), + float3(-FLT_MAX, -FLT_MAX, -FLT_MAX)}; + + bool do_orig = true; // XXX updateflag & PBVH_UpdateOriginalBB; + bool do_normal = true; // XXX updateflag & PBVH_UpdateBB; + if (node->flag & PBVH_Leaf) { - switch (pbvh->header.type) { - case PBVH_FACES: - update_node_bounds_mesh(pbvh->vert_positions, *node); - break; - case PBVH_GRIDS: - update_node_bounds_grids(pbvh->gridkey, pbvh->subdiv_ccg->grids, *node); - break; - case PBVH_BMESH: - update_node_bounds_bmesh(*node); - break; + PBVHVertexIter vd; + + BKE_pbvh_vertex_iter_begin (pbvh, node, vd, PBVH_ITER_ALL) { + if (do_normal) { + vb.min = math::min(vb.min, float3(vd.co)); + vb.max = math::max(vb.max, float3(vd.co)); + } + + if (do_orig) { + const float *origco = pbvh->header.type == PBVH_BMESH ? + BM_ELEM_CD_PTR(vd.bm_vert, pbvh->cd_origco) : + reinterpret_cast(&pbvh->origco[vd.index]); + + /* XXX check stroke id here and use v->co? */ + orig_vb.min = math::min(orig_vb.min, float3(origco)); + orig_vb.max = math::max(orig_vb.max, float3(origco)); + } + } + BKE_pbvh_vertex_iter_end; + + if (!not_leaf_or_has_faces(node)) { + vb.min = float3(); + vb.max = float3(); + orig_vb.min = float3(); + orig_vb.max = float3(); } } else { - node->vb = bounds::merge(pbvh->nodes[node->children_offset].vb, - pbvh->nodes[node->children_offset + 1].vb); + bool ok = false; + + if (not_leaf_or_has_faces(&pbvh->nodes[node->children_offset])) { + if (do_normal) { + vb = bounds::merge(vb, pbvh->nodes[node->children_offset].vb); + } + if (do_orig) { + orig_vb = bounds::merge(orig_vb, pbvh->nodes[node->children_offset].orig_vb); + } + + ok = true; + } + + if (not_leaf_or_has_faces(&pbvh->nodes[node->children_offset + 1])) { + if (do_normal) { + vb = bounds::merge(vb, pbvh->nodes[node->children_offset + 1].vb); + } + if (do_orig) { + orig_vb = bounds::merge(orig_vb, pbvh->nodes[node->children_offset + 1].orig_vb); + } + + ok = true; + } + + if (!ok) { + vb.min = float3(); + vb.max = float3(); + orig_vb.min = float3(); + orig_vb.max = float3(); + } + } + + if (do_normal) { + node->vb = vb; + } + + if (do_orig) { + node->orig_vb = orig_vb; } } @@ -314,6 +457,7 @@ static void update_vb(const Span prim_indices, node->orig_vb = node->vb; } +namespace blender::bke::pbvh { int count_grid_quads(const BitGroupVector<> &grid_hidden, const Span grid_indices, int gridsize, @@ -347,13 +491,14 @@ int count_grid_quads(const BitGroupVector<> &grid_hidden, return totquad; } +} // namespace blender::bke::pbvh static void build_grid_leaf_node(PBVH *pbvh, PBVHNode *node) { - int totquads = count_grid_quads(pbvh->subdiv_ccg->grid_hidden, - node->prim_indices, - pbvh->gridkey.grid_size, - pbvh->gridkey.grid_size); + int totquads = bke::pbvh::count_grid_quads(pbvh->subdiv_ccg->grid_hidden, + node->prim_indices, + pbvh->gridkey.grid_size, + pbvh->gridkey.grid_size); BKE_pbvh_node_fully_hidden_set(node, (totquads == 0)); BKE_pbvh_node_mark_rebuild_draw(node); } @@ -480,7 +625,7 @@ static void build_sub(PBVH *pbvh, } /* Decide whether this is a leaf or not */ - const bool below_leaf_limit = count <= pbvh->leaf_limit || depth >= STACK_FIXED_DEPTH - 1; + const bool below_leaf_limit = count <= pbvh->leaf_limit || depth >= PBVH_STACK_FIXED_DEPTH - 1; if (below_leaf_limit) { if (!leaf_needs_material_split( pbvh, prim_to_face_map, material_indices, sharp_faces, offset, count)) @@ -621,16 +766,183 @@ static void pbvh_build(PBVH *pbvh, 0); } -#ifdef VALIDATE_UNIQUE_NODE_FACES -static void pbvh_validate_node_prims(PBVH *pbvh, const Span tri_faces) +void BKE_pbvh_set_face_areas(PBVH *pbvh, float *face_areas) { - int totface = 0; + pbvh->face_areas = face_areas; +} + +void BKE_pbvh_show_orig_set(PBVH *pbvh, bool show_orig) +{ + pbvh->show_orig = show_orig; +} + +bool BKE_pbvh_show_orig_get(PBVH *pbvh) +{ + return pbvh->show_orig; +} + +#if 0 +static void pbvh_draw_args_init(const Mesh &mesh, PBVH *pbvh, PBVH_GPU_Args *args, PBVHNode *node) +{ + memset((void *)args, 0, sizeof(*args)); + + args->pbvh_type = pbvh->header.type; + args->mesh_grids_num = pbvh->totgrid; + args->node = node; + args->origco = pbvh->origco; + args->origno = pbvh->origno; + + args->grid_hidden = pbvh->grid_hidden; + args->face_sets_color_default = mesh.face_sets_color_default; + args->face_sets_color_seed = mesh.face_sets_color_seed; + args->vert_positions = pbvh->vert_positions; + if (pbvh->mesh) { + args->corner_verts = pbvh->corner_verts; + args->corner_edges = pbvh->mesh->corner_edges(); + } + args->faces = pbvh->faces; + args->updategen = node->updategen; + + if (ELEM(pbvh->header.type, PBVH_FACES, PBVH_GRIDS)) { + args->hide_poly = pbvh->face_data ? static_cast(CustomData_get_layer_named( + pbvh->face_data, CD_PROP_BOOL, ".hide_poly")) : + nullptr; + } + + args->active_color = mesh.active_color_attribute; + args->render_color = mesh.default_color_attribute; + + switch (pbvh->header.type) { + case PBVH_FACES: + args->vert_data = pbvh->vert_data; + args->corner_data = pbvh->corner_data; + args->face_data = pbvh->face_data; + args->me = pbvh->mesh; + args->faces = pbvh->faces; + args->vert_normals = pbvh->vert_normals; + args->face_normals = pbvh->face_normals; + + args->prim_indices = node->prim_indices; + args->corner_tris = pbvh->corner_tris; + args->tri_faces = pbvh->corner_tri_faces; + break; + case PBVH_GRIDS: + args->vert_data = pbvh->vert_data; + args->corner_data = pbvh->corner_data; + args->face_data = pbvh->face_data; + args->ccg_key = pbvh->gridkey; + args->me = pbvh->mesh; + args->grid_indices = node->prim_indices; + args->subdiv_ccg = pbvh->subdiv_ccg; + args->faces = pbvh->faces; + args->corner_tris = pbvh->corner_tris; + args->tri_faces = pbvh->corner_tri_faces; + + args->mesh_grids_num = pbvh->totgrid; + args->grids = pbvh->grids; + args->vert_normals = pbvh->vert_normals; + break; + case PBVH_BMESH: + args->bm = pbvh->header.bm; + args->active_color = pbvh->mesh->active_color_attribute; + args->render_color = pbvh->mesh->default_color_attribute; + + args->vert_data = &args->bm->vdata; + args->corner_data = &args->bm->ldata; + args->face_data = &args->bm->pdata; + args->bm_faces = node->bm_faces; + args->bm_other_verts = node->bm_other_verts; + args->bm_unique_verts = node->bm_unique_verts; + args->cd_mask_layer = CustomData_get_offset_named( + &pbvh->header.bm->vdata, CD_PROP_FLOAT, ".sculpt_mask"); + + args->tribuf = node->tribuf; + args->tri_buffers = node->tri_buffers->data(); + args->tot_tri_buffers = node->tri_buffers->size(); + + args->show_orig = pbvh->show_orig; + break; + } +} +#endif + +static blender::draw::pbvh::PBVH_GPU_Args pbvh_draw_args_init(const Mesh &mesh, + PBVH &pbvh, + const PBVHNode &node) +{ + /* TODO: Use an explicit argument for the original mesh to avoid relying on #PBVH::mesh. */ + blender::draw::pbvh::PBVH_GPU_Args args{}; + + args.pbvh_type = pbvh.header.type; + + args.face_sets_color_default = pbvh.mesh ? pbvh.mesh->face_sets_color_default : + mesh.face_sets_color_default; + args.face_sets_color_seed = pbvh.mesh ? pbvh.mesh->face_sets_color_seed : + mesh.face_sets_color_seed; + + args.active_color = mesh.active_color_attribute; + args.render_color = mesh.default_color_attribute; + + switch (pbvh.header.type) { + case PBVH_FACES: + args.vert_data = &mesh.vert_data; + args.corner_data = &mesh.corner_data; + args.face_data = &mesh.face_data; + args.mesh = pbvh.mesh; + args.vert_positions = pbvh.vert_positions; + args.corner_verts = mesh.corner_verts(); + args.corner_edges = mesh.corner_edges(); + args.corner_tris = pbvh.corner_tris; + args.vert_normals = pbvh.vert_normals; + args.face_normals = pbvh.face_normals; + /* Retrieve data from the original mesh. Ideally that would be passed to this function to + * make it clearer when each is used. */ + args.hide_poly = *pbvh.mesh->attributes().lookup(".hide_poly", AttrDomain::Face); + + args.prim_indices = node.prim_indices; + args.tri_faces = mesh.corner_tri_faces(); + break; + case PBVH_GRIDS: + args.vert_data = &pbvh.mesh->vert_data; + args.corner_data = &pbvh.mesh->corner_data; + args.face_data = &pbvh.mesh->face_data; + args.ccg_key = pbvh.gridkey; + args.mesh = pbvh.mesh; + args.grid_indices = node.prim_indices; + args.subdiv_ccg = pbvh.subdiv_ccg; + args.grids = pbvh.subdiv_ccg->grids; + args.vert_normals = pbvh.vert_normals; + break; + case PBVH_BMESH: + args.bm = pbvh.header.bm; + args.vert_data = &args.bm->vdata; + args.corner_data = &args.bm->ldata; + args.face_data = &args.bm->pdata; + args.bm_faces = node.bm_faces; + args.cd_mask_layer = CustomData_get_offset_named( + &pbvh.header.bm->vdata, CD_PROP_FLOAT, ".sculpt_mask"); + + args.tribuf = node.tribuf; + args.tri_buffers = *node.tri_buffers; + + break; + } + + args.show_orig = pbvh.show_orig; + + return args; +} + +#ifdef VALIDATE_UNIQUE_NODE_FACES +static void pbvh_validate_node_prims(PBVH *pbvh) +{ + int faces_face = 0; if (pbvh->header.type == PBVH_BMESH) { return; } - for (int i = 0; i < pbvh->totnode; i++) { + for (int i = 0; i < pbvh->nodes.size(); i++) { PBVHNode *node = pbvh->nodes + i; if (!(node->flag & PBVH_Leaf)) { @@ -641,23 +953,23 @@ static void pbvh_validate_node_prims(PBVH *pbvh, const Span tri_faces) int face_i; if (pbvh->header.type == PBVH_FACES) { - face_i = tri_faces[node->prim_indices[j]]; + face_i = pbvh->corner_tri_faces[node->prim_indices[j]]; } else { face_i = BKE_subdiv_ccg_grid_to_face_index(pbvh->subdiv_ccg, node->prim_indices[j]); } - totface = max_ii(totface, face_i + 1); + faces_face = max_ii(faces_face, face_i + 1); } } - int *facemap = (int *)MEM_malloc_arrayN(totface, sizeof(*facemap), __func__); + int *facemap = (int *)MEM_malloc_arrayN(faces_face, sizeof(*facemap), __func__); - for (int i = 0; i < totface; i++) { + for (int i = 0; i < faces_face; i++) { facemap[i] = -1; } - for (int i = 0; i < pbvh->totnode; i++) { + for (int i = 0; i < pbvh->nodes.size(); i++) { PBVHNode *node = pbvh->nodes + i; if (!(node->flag & PBVH_Leaf)) { @@ -668,7 +980,7 @@ static void pbvh_validate_node_prims(PBVH *pbvh, const Span tri_faces) int face_i; if (pbvh->header.type == PBVH_FACES) { - face_i = tri_faces[node->prim_indices[j]]; + face_i = pbvh->corner_tri_faces[node->prim_indices[j]]; } else { face_i = BKE_subdiv_ccg_grid_to_face_index(pbvh->subdiv_ccg, node->prim_indices[j]); @@ -688,12 +1000,21 @@ static void pbvh_validate_node_prims(PBVH *pbvh, const Span tri_faces) } #endif -void update_mesh_pointers(PBVH *pbvh, Mesh *mesh) +void BKE_pbvh_update_mesh_pointers(PBVH *pbvh, Mesh *mesh) { BLI_assert(pbvh->header.type == PBVH_FACES); + pbvh->faces = mesh->faces(); + pbvh->edges = mesh->edges(); pbvh->corner_verts = mesh->corner_verts(); + pbvh->corner_edges = mesh->corner_edges(); pbvh->corner_tri_faces = mesh->corner_tri_faces(); + + pbvh->seam_edges = static_cast( + CustomData_get_layer_named(&mesh->edge_data, CD_PROP_BOOL, ".uv_seam")); + pbvh->sharp_edges = static_cast( + CustomData_get_layer_named(&mesh->edge_data, CD_PROP_BOOL, "sharp_edge")); + if (!pbvh->deformed) { /* Deformed data not matching the original mesh are owned directly by the PBVH, and are * set separately by #BKE_pbvh_vert_coords_apply. */ @@ -701,11 +1022,21 @@ void update_mesh_pointers(PBVH *pbvh, Mesh *mesh) pbvh->vert_normals = mesh->vert_normals(); pbvh->face_normals = mesh->face_normals(); } + + pbvh->face_areas = static_cast(CustomData_get_layer_named_for_write( + &mesh->face_data, CD_PROP_FLOAT2, SCULPT_ATTRIBUTE_NAME(face_areas), mesh->faces_num)); + + BKE_pbvh_update_hide_attributes_from_mesh(pbvh); + + pbvh->vert_data = &mesh->vert_data; + pbvh->corner_data = &mesh->corner_data; + pbvh->face_data = &mesh->face_data; } +namespace blender::bke::pbvh { PBVH *build_mesh(Mesh *mesh) { - std::unique_ptr pbvh = std::make_unique(); + PBVH *pbvh = MEM_new(__func__); pbvh->header.type = PBVH_FACES; const int totvert = mesh->verts_num; @@ -720,7 +1051,7 @@ PBVH *build_mesh(Mesh *mesh) pbvh->mesh = mesh; - update_mesh_pointers(pbvh.get(), mesh); + BKE_pbvh_update_mesh_pointers(pbvh, mesh); const Span tri_faces = pbvh->corner_tri_faces; Array vert_bitmap(totvert, false); @@ -761,7 +1092,7 @@ PBVH *build_mesh(Mesh *mesh) const VArraySpan hide_poly = *attributes.lookup(".hide_poly", AttrDomain::Face); const VArraySpan material_index = *attributes.lookup("material_index", AttrDomain::Face); const VArraySpan sharp_face = *attributes.lookup("sharp_face", AttrDomain::Face); - pbvh_build(pbvh.get(), + pbvh_build(pbvh, corner_verts, corner_tris, tri_faces, @@ -778,18 +1109,18 @@ PBVH *build_mesh(Mesh *mesh) #endif } - BKE_pbvh_update_active_vcol(pbvh.get(), mesh); + BKE_pbvh_update_active_vcol(pbvh, mesh); #ifdef VALIDATE_UNIQUE_NODE_FACES pbvh_validate_node_prims(pbvh); #endif - return pbvh.release(); + return pbvh; } PBVH *build_grids(const CCGKey *key, Mesh *mesh, SubdivCCG *subdiv_ccg) { - std::unique_ptr pbvh = std::make_unique(); + PBVH *pbvh = MEM_new(__func__); pbvh->header.type = PBVH_GRIDS; pbvh->gridkey = *key; @@ -839,52 +1170,71 @@ PBVH *build_grids(const CCGKey *key, Mesh *mesh, SubdivCCG *subdiv_ccg) const AttributeAccessor attributes = mesh->attributes(); const VArraySpan material_index = *attributes.lookup("material_index", AttrDomain::Face); const VArraySpan sharp_face = *attributes.lookup("sharp_face", AttrDomain::Face); - pbvh_build(pbvh.get(), - {}, - {}, - {}, - {}, - material_index, - sharp_face, - {}, - &cb, - prim_bounds, - grids.size()); + pbvh_build( + pbvh, {}, {}, {}, {}, material_index, sharp_face, {}, &cb, prim_bounds, grids.size()); } #ifdef VALIDATE_UNIQUE_NODE_FACES pbvh_validate_node_prims(pbvh); #endif - return pbvh.release(); + return pbvh; +} +} // namespace blender::bke::pbvh + +PBVH *BKE_pbvh_new(PBVHType type) +{ + PBVH *pbvh = MEM_new(__func__); + pbvh->draw_cache_invalid = true; + pbvh->header.type = type; + + /* Initialize this to true, instead of waiting for a draw engine + * to set it. Prevents a crash in draw manager instancing code. + */ + pbvh->is_drawing = true; + return pbvh; } +namespace blender::bke::pbvh { void free(PBVH *pbvh) { for (PBVHNode &node : pbvh->nodes) { if (node.flag & PBVH_Leaf) { if (node.draw_batches) { - blender::draw::pbvh::node_free(node.draw_batches); + draw::pbvh::node_free(node.draw_batches); + } + if (node.bm_faces) { + MEM_delete>(node.bm_faces); + } + if (node.bm_unique_verts) { + MEM_delete>(node.bm_unique_verts); + } + if (node.bm_other_verts) { + MEM_delete>(node.bm_other_verts); } - } - if (node.flag & (PBVH_Leaf | PBVH_TexLeaf)) { - node_pixels_free(&node); + if (node.tribuf || node.tri_buffers) { + BKE_pbvh_bmesh_free_tris(pbvh, &node); + } + + if (node.flag & (PBVH_Leaf | PBVH_TexLeaf)) { + blender::bke::pbvh::node_pixels_free(&node); + } } } - pixels_free(pbvh); - - delete pbvh; + blender::bke::pbvh::pixels_free(pbvh); + MEM_delete(pbvh); } +} // namespace blender::bke::pbvh -static void pbvh_iter_begin(PBVHIter *iter, PBVH *pbvh, FunctionRef scb) +static void pbvh_iter_begin(PBVHIter *iter, PBVH *pbvh, blender::FunctionRef scb) { iter->pbvh = pbvh; iter->scb = scb; iter->stack = iter->stackfixed; - iter->stackspace = STACK_FIXED_DEPTH; + iter->stackspace = PBVH_STACK_FIXED_DEPTH; iter->stack[0].node = &pbvh->nodes.first(); iter->stack[0].revisiting = false; @@ -893,7 +1243,7 @@ static void pbvh_iter_begin(PBVHIter *iter, PBVH *pbvh, FunctionRefstackspace > STACK_FIXED_DEPTH) { + if (iter->stackspace > PBVH_STACK_FIXED_DEPTH) { MEM_freeN(iter->stack); } } @@ -902,14 +1252,12 @@ static void pbvh_stack_push(PBVHIter *iter, PBVHNode *node, bool revisiting) { if (UNLIKELY(iter->stacksize == iter->stackspace)) { iter->stackspace *= 2; - if (iter->stackspace != (STACK_FIXED_DEPTH * 2)) { - iter->stack = static_cast( - MEM_reallocN(iter->stack, sizeof(PBVHStack) * iter->stackspace)); + if (iter->stackspace != (PBVH_STACK_FIXED_DEPTH * 2)) { + iter->stack = (PBVHStack *)MEM_reallocN(iter->stack, sizeof(PBVHStack) * iter->stackspace); } else { - iter->stack = static_cast( - MEM_mallocN(sizeof(PBVHStack) * iter->stackspace, "PBVHStack")); - memcpy(iter->stack, iter->stackfixed, sizeof(PBVHStack) * iter->stacksize); + iter->stack = (PBVHStack *)MEM_mallocN(sizeof(PBVHStack) * iter->stackspace, "PBVHStack"); + memcpy((void *)iter->stack, (void *)iter->stackfixed, sizeof(PBVHStack) * iter->stacksize); } } @@ -918,7 +1266,7 @@ static void pbvh_stack_push(PBVHIter *iter, PBVHNode *node, bool revisiting) iter->stacksize++; } -static PBVHNode *pbvh_iter_next(PBVHIter *iter, PBVHNodeFlags leaf_flag) +static PBVHNode *pbvh_iter_next(PBVHIter *iter, PBVHNodeFlags leaf_flag = PBVH_Leaf) { /* purpose here is to traverse tree, visiting child nodes before their * parents, this order is necessary for e.g. computing bounding boxes */ @@ -974,6 +1322,14 @@ static PBVHNode *pbvh_iter_next_occluded(PBVHIter *iter) return nullptr; } + float ff = dot_v3v3(node->vb.min, node->vb.max); + if (isnan(ff) || !isfinite(ff)) { + printf("%s: nan! totf: %d totv: %d\n", + __func__, + node->bm_faces ? node->bm_faces->size() : 0, + node->bm_unique_verts ? node->bm_unique_verts->size() : 0); + } + if (iter->scb && !iter->scb(*node)) { continue; /* don't traverse, outside of search zone */ } @@ -993,8 +1349,8 @@ static PBVHNode *pbvh_iter_next_occluded(PBVHIter *iter) struct node_tree { PBVHNode *data; - node_tree *left; - node_tree *right; + struct node_tree *left; + struct node_tree *right; }; static void node_tree_insert(node_tree *tree, node_tree *new_node) @@ -1044,18 +1400,15 @@ static void free_tree(node_tree *tree) tree->right = nullptr; } - ::free(tree); + free(tree); } -} // namespace blender::bke::pbvh - -float BKE_pbvh_node_get_tmin(const PBVHNode *node) +float BKE_pbvh_node_get_tmin(PBVHNode *node) { return node->tmin; } namespace blender::bke::pbvh { - void search_callback(PBVH &pbvh, FunctionRef filter_fn, FunctionRef hit_fn) @@ -1076,6 +1429,7 @@ void search_callback(PBVH &pbvh, pbvh_iter_end(&iter); } +} // namespace blender::bke::pbvh static void search_callback_occluded(PBVH *pbvh, const FunctionRef scb, @@ -1126,6 +1480,19 @@ static bool update_search(PBVHNode *node, const int flag) return true; } +struct PBVHUpdateData { + PBVH *pbvh; + Mesh *mesh; + Span nodes; + + int flag = 0; + bool show_sculpt_face_sets = false; + PBVHAttrReq *attrs = nullptr; + int attrs_num = 0; + + PBVHUpdateData(PBVH *pbvh_, Span nodes_) : pbvh(pbvh_), nodes(nodes_) {} +}; + static void normals_calc_faces(const Span positions, const OffsetIndices faces, const Span corner_verts, @@ -1162,7 +1529,7 @@ static void calc_node_face_normals(const Span positions, normals_calc_faces(positions, faces, corner_verts, - node_face_indices_calc_mesh(pbvh, *node, node_faces), + pbvh::node_face_indices_calc_mesh(pbvh, *node, node_faces), face_normals); } }); @@ -1277,25 +1644,34 @@ static void update_normals_faces(PBVH &pbvh, Span nodes, Mesh &mesh) } } -void update_normals(PBVH &pbvh, SubdivCCG *subdiv_ccg) +static void node_update_mask_redraw(PBVH &pbvh, PBVHNode &node) { - Vector nodes = search_gather( - &pbvh, [&](PBVHNode &node) { return update_search(&node, PBVH_UpdateNormals); }); + if (!(node.flag & PBVH_UpdateMask)) { + return; + } + node.flag &= ~PBVH_UpdateMask; - if (pbvh.header.type == PBVH_BMESH) { - bmesh_normals_update(nodes); - } - else if (pbvh.header.type == PBVH_FACES) { - update_normals_faces(pbvh, nodes, *pbvh.mesh); - } - else if (pbvh.header.type == PBVH_GRIDS) { - IndexMaskMemory memory; - const IndexMask faces_to_update = nodes_to_face_selection_grids(*subdiv_ccg, nodes, memory); - BKE_subdiv_ccg_update_normals(*subdiv_ccg, faces_to_update); - for (PBVHNode *node : nodes) { - node->flag &= ~PBVH_UpdateNormals; + bool has_unmasked = false; + bool has_masked = true; + if (node.flag & PBVH_Leaf) { + PBVHVertexIter vd; + + BKE_pbvh_vertex_iter_begin (&pbvh, &node, vd, PBVH_ITER_ALL) { + if (vd.mask < 1.0f) { + has_unmasked = true; + } + if (vd.mask > 0.0f) { + has_masked = false; + } } + BKE_pbvh_vertex_iter_end; } + else { + has_unmasked = true; + has_masked = true; + } + BKE_pbvh_node_fully_masked_set(&node, !has_unmasked); + BKE_pbvh_node_fully_unmasked_set(&node, has_masked); } static void node_update_bounds(PBVH &pbvh, PBVHNode &node, const PBVHNodeFlags flag) @@ -1303,7 +1679,7 @@ static void node_update_bounds(PBVH &pbvh, PBVHNode &node, const PBVHNodeFlags f if ((flag & PBVH_UpdateBB) && (node.flag & PBVH_UpdateBB)) { /* don't clear flag yet, leave it for flushing later */ /* Note that bvh usage is read-only here, so no need to thread-protect it. */ - update_node_vb(&pbvh, &node); + update_node_vb(&pbvh, &node, flag); } if ((flag & PBVH_UpdateOriginalBB) && (node.flag & PBVH_UpdateOriginalBB)) { @@ -1317,6 +1693,7 @@ static void node_update_bounds(PBVH &pbvh, PBVHNode &node, const PBVHNodeFlags f static void pbvh_update_BB_redraw(PBVH *pbvh, Span nodes, int flag) { + using namespace blender; threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { for (PBVHNode *node : nodes.slice(range)) { node_update_bounds(*pbvh, *node, PBVHNodeFlags(flag)); @@ -1324,6 +1701,139 @@ static void pbvh_update_BB_redraw(PBVH *pbvh, Span nodes, int flag) }); } +bool BKE_pbvh_get_color_layer(PBVH *pbvh, + Mesh *me, + CustomDataLayer **r_layer, + AttrDomain *r_domain) +{ + *r_layer = BKE_id_attribute_search_for_write( + &me->id, me->active_color_attribute, CD_MASK_COLOR_ALL, ATTR_DOMAIN_MASK_COLOR); + + if (!*r_layer || !ELEM((*r_layer)->type, CD_PROP_COLOR, CD_PROP_BYTE_COLOR)) { + *r_layer = nullptr; + *r_domain = AttrDomain::Point; + return false; + } + + AttrDomain domain = BKE_id_attribute_domain(&me->id, *r_layer); + + if (!ELEM(domain, AttrDomain::Point, AttrDomain::Corner)) { + *r_layer = nullptr; + *r_domain = AttrDomain::Point; + return false; + } + + if (pbvh && BKE_pbvh_type(pbvh) == PBVH_BMESH) { + CustomData *data; + + if (domain == AttrDomain::Point) { + data = &pbvh->header.bm->vdata; + } + else if (domain == AttrDomain::Corner) { + data = &pbvh->header.bm->ldata; + } + else { + *r_layer = nullptr; + *r_domain = AttrDomain::Point; + + BLI_assert_unreachable(); + return false; + } + + int layer_i = CustomData_get_named_layer_index( + data, eCustomDataType((*r_layer)->type), (*r_layer)->name); + if (layer_i == -1) { + printf("%s: bmesh lacks color attribute %s\n", __func__, (*r_layer)->name); + + *r_layer = nullptr; + *r_domain = AttrDomain::Point; + return false; + } + + *r_layer = &data->layers[layer_i]; + } + + *r_domain = domain; + + return true; +} + +static void node_update_draw_buffers(const Mesh &mesh, PBVH &pbvh, PBVHNode &node) +{ + /* Create and update draw buffers. The functions called here must not + * do any OpenGL calls. Flags are not cleared immediately, that happens + * after GPU_pbvh_buffer_flush() which does the final OpenGL calls. */ + + if (node.flag & PBVH_RebuildDrawBuffers) { + PBVH_GPU_Args args = pbvh_draw_args_init(mesh, pbvh, node); + node.draw_batches = draw::pbvh::node_create(args); + } + + if (node.flag & PBVH_UpdateDrawBuffers) { + node.updategen++; + node.debug_draw_gen++; + + if (node.draw_batches) { + PBVH_GPU_Args args = pbvh_draw_args_init(mesh, pbvh, node); + draw::pbvh::node_update(node.draw_batches, args); + } + } +} + +namespace blender::bke::pbvh { +void free_draw_buffers(PBVH * /* pbvh */, PBVHNode *node) +{ + if (node->draw_batches) { + blender::draw::pbvh::node_free(node->draw_batches); + node->draw_batches = nullptr; + } +} +} // namespace blender::bke::pbvh + +static void pbvh_update_draw_buffers(const Mesh &mesh, + PBVH *pbvh, + Span nodes, + int update_flag) +{ + using namespace blender; + if (pbvh->header.type == PBVH_BMESH && !pbvh->header.bm) { + /* BMesh hasn't been created yet */ + return; + } + + if ((update_flag & PBVH_RebuildDrawBuffers) || ELEM(pbvh->header.type, PBVH_GRIDS, PBVH_BMESH)) { + /* Free buffers uses OpenGL, so not in parallel. */ + for (PBVHNode *node : nodes) { + if (node->flag & PBVH_RebuildDrawBuffers) { + blender::bke::pbvh::free_draw_buffers(pbvh, node); + } + else if ((node->flag & PBVH_UpdateDrawBuffers) && node->draw_batches) { + PBVH_GPU_Args args = pbvh_draw_args_init(mesh, *pbvh, *node); + blender::draw::pbvh::update_pre(node->draw_batches, args); + } + } + } + + /* Parallel creation and update of draw buffers. */ + + threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { + for (PBVHNode *node : nodes.slice(range)) { + node_update_draw_buffers(mesh, *pbvh, *node); + } + }); + + /* Flush buffers uses OpenGL, so not in parallel. */ + for (PBVHNode *node : nodes) { + if (node->flag & PBVH_UpdateDrawBuffers) { + if (node->draw_batches) { + draw::pbvh::node_gpu_flush(node->draw_batches); + } + } + + node->flag &= ~(PBVH_RebuildDrawBuffers | PBVH_UpdateDrawBuffers); + } +} + static int pbvh_flush_bb(PBVH *pbvh, PBVHNode *node, int flag) { int update = 0; @@ -1346,24 +1856,21 @@ static int pbvh_flush_bb(PBVH *pbvh, PBVHNode *node, int flag) update |= pbvh_flush_bb(pbvh, &pbvh->nodes[node->children_offset], flag); update |= pbvh_flush_bb(pbvh, &pbvh->nodes[node->children_offset + 1], flag); - if (update & PBVH_UpdateBB) { - update_node_vb(pbvh, node); - } - if (update & PBVH_UpdateOriginalBB) { - node->orig_vb = node->vb; - } + update_node_vb(pbvh, node, update); return update; } +namespace blender::bke::pbvh { void update_bounds(PBVH &pbvh, int flag) { - Vector nodes = search_gather( - &pbvh, [&](PBVHNode &node) { return update_search(&node, flag); }); - if (nodes.is_empty()) { + if (pbvh.nodes.is_empty()) { return; } + Vector nodes = blender::bke::pbvh::search_gather( + &pbvh, [&](PBVHNode &node) { return update_search(&node, flag); }); + if (flag & (PBVH_UpdateBB | PBVH_UpdateOriginalBB | PBVH_UpdateRedraw)) { pbvh_update_BB_redraw(&pbvh, nodes, flag); } @@ -1372,7 +1879,9 @@ void update_bounds(PBVH &pbvh, int flag) pbvh_flush_bb(&pbvh, &pbvh.nodes.first(), flag); } } +} // namespace blender::bke::pbvh +namespace blender::bke::pbvh { void node_update_mask_mesh(const Span mask, PBVHNode &node) { const bool fully_masked = std::all_of(node.vert_indices.begin(), @@ -1386,26 +1895,6 @@ void node_update_mask_mesh(const Span mask, PBVHNode &node) node.flag &= ~PBVH_UpdateMask; } -static void update_mask_mesh(const Mesh &mesh, const Span nodes) -{ - const AttributeAccessor attributes = mesh.attributes(); - const VArraySpan mask = *attributes.lookup(".sculpt_mask", AttrDomain::Point); - if (mask.is_empty()) { - for (PBVHNode *node : nodes) { - node->flag &= ~PBVH_FullyMasked; - node->flag |= PBVH_FullyUnmasked; - node->flag &= ~PBVH_UpdateMask; - } - return; - } - - threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { - for (PBVHNode *node : nodes.slice(range)) { - node_update_mask_mesh(mask, *node); - } - }); -} - void node_update_mask_grids(const CCGKey &key, const Span grids, PBVHNode &node) { BLI_assert(key.has_mask); @@ -1424,35 +1913,16 @@ void node_update_mask_grids(const CCGKey &key, const Span grids, PBVH node.flag &= ~PBVH_UpdateMask; } -static void update_mask_grids(const SubdivCCG &subdiv_ccg, const Span nodes) -{ - const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg); - if (!key.has_mask) { - for (PBVHNode *node : nodes) { - node->flag &= ~PBVH_FullyMasked; - node->flag |= PBVH_FullyUnmasked; - node->flag &= ~PBVH_UpdateMask; - } - return; - } - - threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { - for (PBVHNode *node : nodes.slice(range)) { - node_update_mask_grids(key, subdiv_ccg.grids, *node); - } - }); -} - void node_update_mask_bmesh(const int mask_offset, PBVHNode &node) { BLI_assert(mask_offset != -1); bool fully_masked = true; bool fully_unmasked = true; - for (const BMVert *vert : node.bm_unique_verts) { + for (const BMVert *vert : *node.bm_unique_verts) { fully_masked &= BM_ELEM_CD_GET_FLOAT(vert, mask_offset) == 1.0f; fully_unmasked &= BM_ELEM_CD_GET_FLOAT(vert, mask_offset) <= 0.0f; } - for (const BMVert *vert : node.bm_other_verts) { + for (const BMVert *vert : *node.bm_other_verts) { fully_masked &= BM_ELEM_CD_GET_FLOAT(vert, mask_offset) == 1.0f; fully_unmasked &= BM_ELEM_CD_GET_FLOAT(vert, mask_offset) <= 0.0f; } @@ -1461,39 +1931,113 @@ void node_update_mask_bmesh(const int mask_offset, PBVHNode &node) node.flag &= ~PBVH_UpdateMask; } -static void update_mask_bmesh(const BMesh &bm, const Span nodes) +void update_mask(PBVH &pbvh) { - const int offset = CustomData_get_offset_named(&bm.vdata, CD_PROP_FLOAT, ".sculpt_mask"); - if (offset == -1) { - for (PBVHNode *node : nodes) { - node->flag &= ~PBVH_FullyMasked; - node->flag |= PBVH_FullyUnmasked; - node->flag &= ~PBVH_UpdateMask; - } - return; - } + using namespace blender; + Vector nodes = blender::bke::pbvh::search_gather( + &pbvh, [&](PBVHNode &node) { return update_search(&node, PBVH_UpdateMask); }); threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { - for (PBVHNode *node : nodes.slice(range)) { - node_update_mask_bmesh(offset, *node); + for (PBVHNode *node : nodes.as_span().slice(range)) { + node_update_mask_redraw(pbvh, *node); } }); } -void update_mask(PBVH &pbvh) +void update_vertex_data(PBVH &pbvh, int flag) { - Vector nodes = search_gather( - &pbvh, [&](PBVHNode &node) { return update_search(&node, PBVH_UpdateMask); }); + using namespace blender; + Vector nodes = blender::bke::pbvh::search_gather( + &pbvh, [&](PBVHNode &node) { return update_search(&node, flag); }); + if (flag & (PBVH_UpdateColor)) { + for (PBVHNode *node : nodes) { + node->flag |= PBVH_UpdateRedraw | PBVH_UpdateDrawBuffers | PBVH_UpdateColor; + } + } +} + +static void pbvh_faces_node_visibility_update(PBVH *pbvh, PBVHNode *node) +{ + if (pbvh->hide_vert == nullptr) { + BKE_pbvh_node_fully_hidden_set(node, false); + return; + } + for (const int vert : node->vert_indices) { + if (!(pbvh->hide_vert[vert])) { + BKE_pbvh_node_fully_hidden_set(node, false); + return; + } + } + + BKE_pbvh_node_fully_hidden_set(node, true); +} + +static void pbvh_grids_node_visibility_update(PBVH *pbvh, PBVHNode *node) +{ + CCGElem **grids; + const int *grid_indices; + int totgrid, i; + + BKE_pbvh_node_get_grids(pbvh, node, &grid_indices, &totgrid, nullptr, nullptr, &grids); + BitGroupVector<> &grid_hidden = *BKE_pbvh_grid_hidden(pbvh); + CCGKey key = *BKE_pbvh_get_grid_key(pbvh); + + for (i = 0; i < totgrid; i++) { + int g = grid_indices[i], x, y; + blender::BoundedBitSpan gh = grid_hidden[g]; + + if (gh.is_empty()) { + BKE_pbvh_node_fully_hidden_set(node, false); + return; + } + + for (y = 0; y < key.grid_size; y++) { + for (x = 0; x < key.grid_size; x++) { + if (!gh[y * key.grid_size + x]) { + BKE_pbvh_node_fully_hidden_set(node, false); + return; + } + } + } + } + BKE_pbvh_node_fully_hidden_set(node, true); +} + +static void pbvh_bmesh_node_visibility_update(PBVHNode *node) +{ + for (BMVert *v : *node->bm_unique_verts) { + if (!BM_elem_flag_test(v, BM_ELEM_HIDDEN)) { + BKE_pbvh_node_fully_hidden_set(node, false); + return; + } + } + + for (BMVert *v : *node->bm_other_verts) { + if (!BM_elem_flag_test(v, BM_ELEM_HIDDEN)) { + BKE_pbvh_node_fully_hidden_set(node, false); + return; + } + } + + BKE_pbvh_node_fully_hidden_set(node, true); +} + +static void node_update_visibility(PBVH &pbvh, PBVHNode &node) +{ + if (!(node.flag & PBVH_UpdateVisibility)) { + return; + } + node.flag &= ~PBVH_UpdateVisibility; switch (BKE_pbvh_type(&pbvh)) { case PBVH_FACES: - update_mask_mesh(*pbvh.mesh, nodes); + pbvh_faces_node_visibility_update(&pbvh, &node); break; case PBVH_GRIDS: - update_mask_grids(*pbvh.subdiv_ccg, nodes); + pbvh_grids_node_visibility_update(&pbvh, &node); break; case PBVH_BMESH: - update_mask_bmesh(*pbvh.header.bm, nodes); + pbvh_bmesh_node_visibility_update(&node); break; } } @@ -1559,11 +2103,11 @@ static void update_visibility_grids(PBVH &pbvh, const Span nodes) void node_update_visibility_bmesh(PBVHNode &node) { const bool unique_hidden = std::all_of( - node.bm_unique_verts.begin(), node.bm_unique_verts.end(), [&](const BMVert *vert) { + node.bm_unique_verts->begin(), node.bm_unique_verts->end(), [&](const BMVert *vert) { return BM_elem_flag_test(vert, BM_ELEM_HIDDEN); }); const bool other_hidden = std::all_of( - node.bm_other_verts.begin(), node.bm_other_verts.end(), [&](const BMVert *vert) { + node.bm_other_verts->begin(), node.bm_other_verts->end(), [&](const BMVert *vert) { return BM_elem_flag_test(vert, BM_ELEM_HIDDEN); }); SET_FLAG_FROM_TEST(node.flag, unique_hidden && other_hidden, PBVH_FullyHidden); @@ -1596,29 +2140,28 @@ void update_visibility(PBVH &pbvh) break; } } - } // namespace blender::bke::pbvh Bounds BKE_pbvh_redraw_BB(PBVH *pbvh) { - using namespace blender; - using namespace blender::bke::pbvh; if (pbvh->nodes.is_empty()) { - return {}; + return Bounds(); } - Bounds bounds = negative_bounds(); - PBVHIter iter; - pbvh_iter_begin(&iter, pbvh, {}); PBVHNode *node; - while ((node = pbvh_iter_next(&iter, PBVH_Leaf))) { + Bounds bb = negative_bounds(); + + pbvh_iter_begin(&iter, pbvh, {}); + + while ((node = pbvh_iter_next(&iter))) { if (node->flag & PBVH_UpdateRedraw) { - bounds = bounds::merge(bounds, node->vb); + bb = bounds::merge(bb, node->vb); } } + pbvh_iter_end(&iter); - return bounds; + return bb; } namespace blender::bke::pbvh { @@ -1646,20 +2189,28 @@ IndexMask nodes_to_face_selection_grids(const SubdivCCG &subdiv_ccg, /***************************** PBVH Access ***********************************/ -bool BKE_pbvh_get_color_layer(Mesh *mesh, CustomDataLayer **r_layer, AttrDomain *r_domain) +bool BKE_pbvh_has_faces(const PBVH *pbvh) { - *r_layer = BKE_id_attribute_search_for_write( - &mesh->id, mesh->active_color_attribute, CD_MASK_COLOR_ALL, ATTR_DOMAIN_MASK_COLOR); - *r_domain = *r_layer ? BKE_id_attribute_domain(&mesh->id, *r_layer) : AttrDomain::Point; - return *r_layer != nullptr; + if (pbvh->header.type == PBVH_BMESH) { + return (pbvh->header.bm->totface != 0); + } + + return (pbvh->totprim != 0); } Bounds BKE_pbvh_bounding_box(const PBVH *pbvh) { if (pbvh->nodes.is_empty()) { - return float3(0); + return Bounds(); } - return pbvh->nodes.first().vb; + + return pbvh->nodes[0].vb; +} + +BitGroupVector<> *BKE_pbvh_grid_hidden(const PBVH *pbvh) +{ + BLI_assert(pbvh->header.type == PBVH_GRIDS); + return pbvh->grid_hidden; } const CCGKey *BKE_pbvh_get_grid_key(const PBVH *pbvh) @@ -1668,25 +2219,42 @@ const CCGKey *BKE_pbvh_get_grid_key(const PBVH *pbvh) return &pbvh->gridkey; } +struct CCGElem **BKE_pbvh_get_grids(const PBVH *pbvh) +{ + BLI_assert(pbvh->header.type == PBVH_GRIDS); + return pbvh->grids; +} + +BitGroupVector<> *BKE_pbvh_get_grid_visibility(const PBVH *pbvh) +{ + BLI_assert(pbvh->header.type == PBVH_GRIDS); + return pbvh->grid_hidden; +} + int BKE_pbvh_get_grid_num_verts(const PBVH *pbvh) { BLI_assert(pbvh->header.type == PBVH_GRIDS); - return pbvh->subdiv_ccg->grids.size() * pbvh->gridkey.grid_area; + return pbvh->totgrid * pbvh->gridkey.grid_area; } int BKE_pbvh_get_grid_num_faces(const PBVH *pbvh) { BLI_assert(pbvh->header.type == PBVH_GRIDS); - return pbvh->subdiv_ccg->grids.size() * (pbvh->gridkey.grid_size - 1) * - (pbvh->gridkey.grid_size - 1); + return pbvh->totgrid * (pbvh->gridkey.grid_size - 1) * (pbvh->gridkey.grid_size - 1); } /***************************** Node Access ***********************************/ +void BKE_pbvh_node_mark_original_update(PBVHNode *node) +{ + node->flag |= PBVH_UpdateOriginalBB; +} + void BKE_pbvh_node_mark_update(PBVHNode *node) { node->flag |= PBVH_UpdateNormals | PBVH_UpdateBB | PBVH_UpdateOriginalBB | - PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw | PBVH_RebuildPixels; + PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw | PBVH_UpdateCurvatureDir | + PBVH_RebuildPixels | PBVH_UpdateTriAreas; } void BKE_pbvh_node_mark_update_mask(PBVHNode *node) @@ -1716,12 +2284,20 @@ void BKE_pbvh_mark_rebuild_pixels(PBVH *pbvh) void BKE_pbvh_node_mark_update_visibility(PBVHNode *node) { node->flag |= PBVH_UpdateVisibility | PBVH_RebuildDrawBuffers | PBVH_UpdateDrawBuffers | - PBVH_UpdateRedraw; + PBVH_UpdateRedraw | PBVH_UpdateTris | PBVH_UpdateTriAreas; +} + +void BKE_pbvh_vert_tag_update_normal_visibility(PBVHNode *node) +{ + node->flag |= PBVH_UpdateVisibility | PBVH_RebuildDrawBuffers | PBVH_UpdateDrawBuffers | + PBVH_UpdateRedraw | PBVH_UpdateCurvatureDir | PBVH_UpdateTris | + PBVH_UpdateTriAreas; } void BKE_pbvh_node_mark_rebuild_draw(PBVHNode *node) { - node->flag |= PBVH_RebuildDrawBuffers | PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw; + node->flag |= PBVH_RebuildDrawBuffers | PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw | + PBVH_UpdateCurvatureDir | PBVH_UpdateTriAreas; } void BKE_pbvh_node_mark_redraw(PBVHNode *node) @@ -1731,7 +2307,27 @@ void BKE_pbvh_node_mark_redraw(PBVHNode *node) void BKE_pbvh_node_mark_positions_update(PBVHNode *node) { - node->flag |= PBVH_UpdateNormals | PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw | PBVH_UpdateBB; + node->flag |= PBVH_UpdateNormals | PBVH_UpdateCurvatureDir; +} + +void BKE_pbvh_node_mark_curvature_update(PBVHNode *node) +{ + node->flag |= PBVH_UpdateCurvatureDir; +} + +void BKE_pbvh_curvature_update_set(PBVHNode *node, bool state) +{ + if (state) { + node->flag |= PBVH_UpdateCurvatureDir; + } + else { + node->flag &= ~PBVH_UpdateCurvatureDir; + } +} + +bool BKE_pbvh_curvature_update_get(PBVHNode *node) +{ + return node->flag & PBVH_UpdateCurvatureDir; } void BKE_pbvh_node_fully_hidden_set(PBVHNode *node, int fully_hidden) @@ -1780,7 +2376,7 @@ void BKE_pbvh_node_fully_unmasked_set(PBVHNode *node, int fully_masked) } } -bool BKE_pbvh_node_fully_unmasked_get(const PBVHNode *node) +bool BKE_pbvh_node_fully_unmasked_get(PBVHNode *node) { return (node->flag & PBVH_Leaf) && (node->flag & PBVH_FullyUnmasked); } @@ -1800,6 +2396,20 @@ blender::Span BKE_pbvh_node_get_unique_vert_indices(const PBVHNode *node) return node->vert_indices.as_span().take_front(node->uniq_verts); } +int BKE_pbvh_num_faces(const PBVH *pbvh) +{ + switch (pbvh->header.type) { + case PBVH_GRIDS: + case PBVH_FACES: + return pbvh->faces_num; + case PBVH_BMESH: + return pbvh->header.bm->totface; + } + + BLI_assert_unreachable(); + return 0; +} + namespace blender::bke::pbvh { Span node_face_indices_calc_mesh(const PBVH &pbvh, const PBVHNode &node, Vector &faces) @@ -1842,7 +2452,7 @@ int BKE_pbvh_node_num_unique_verts(const PBVH &pbvh, const PBVHNode &node) case PBVH_FACES: return node.uniq_verts; case PBVH_BMESH: - return node.bm_unique_verts.size(); + return node.bm_unique_verts->size(); } BLI_assert_unreachable(); return 0; @@ -1853,6 +2463,53 @@ Span BKE_pbvh_node_get_grid_indices(const PBVHNode &node) return node.prim_indices; } +void BKE_pbvh_node_get_grids(PBVH *pbvh, + PBVHNode *node, + const int **r_grid_indices, + int *r_totgrid, + int *r_maxgrid, + int *r_gridsize, + CCGElem ***r_griddata) +{ + switch (pbvh->header.type) { + case PBVH_GRIDS: + if (r_grid_indices) { + *r_grid_indices = node->prim_indices.data(); + } + if (r_totgrid) { + *r_totgrid = node->prim_indices.size(); + } + if (r_maxgrid) { + *r_maxgrid = pbvh->totgrid; + } + if (r_gridsize) { + *r_gridsize = pbvh->gridkey.grid_size; + } + if (r_griddata) { + *r_griddata = pbvh->grids; + } + break; + case PBVH_FACES: + case PBVH_BMESH: + if (r_grid_indices) { + *r_grid_indices = nullptr; + } + if (r_totgrid) { + *r_totgrid = 0; + } + if (r_maxgrid) { + *r_maxgrid = 0; + } + if (r_gridsize) { + *r_gridsize = 0; + } + if (r_griddata) { + *r_griddata = nullptr; + } + break; + } +} + Bounds BKE_pbvh_node_get_BB(const PBVHNode *node) { return node->vb; @@ -1860,6 +2517,7 @@ Bounds BKE_pbvh_node_get_BB(const PBVHNode *node) Bounds BKE_pbvh_node_get_original_BB(const PBVHNode *node) { + return node->orig_vb; } @@ -1868,29 +2526,13 @@ blender::MutableSpan BKE_pbvh_node_get_proxies(PBVHNode *node) return node->proxies; } -void BKE_pbvh_node_get_bm_orco_data(PBVHNode *node, - int (**r_orco_tris)[3], - int *r_orco_tris_num, - float (**r_orco_coords)[3], - BMVert ***r_orco_verts) -{ - *r_orco_tris = node->bm_ortri; - *r_orco_tris_num = node->bm_tot_ortri; - *r_orco_coords = node->bm_orco; - - if (r_orco_verts) { - *r_orco_verts = node->bm_orvert; - } -} - /********************************* Ray-cast ***********************************/ -namespace blender::bke::pbvh { - -struct RaycastData { - IsectRayAABB_Precalc ray; +typedef struct { + struct IsectRayAABB_Precalc ray; bool original; -}; + int stroke_id; +} RaycastData; static bool ray_aabb_intersect(PBVHNode &node, const RaycastData &rcd) { @@ -1900,23 +2542,27 @@ static bool ray_aabb_intersect(PBVHNode &node, const RaycastData &rcd) return isect_ray_aabb_v3(&rcd.ray, node.vb.min, node.vb.max, &node.tmin); } +namespace blender::bke::pbvh { void raycast(PBVH *pbvh, const FunctionRef hit_fn, const float ray_start[3], const float ray_normal[3], - bool original) + bool original, + int stroke_id) { RaycastData rcd; isect_ray_aabb_v3_precalc(&rcd.ray, ray_start, ray_normal); rcd.original = original; + rcd.stroke_id = stroke_id; + pbvh->stroke_id = stroke_id; search_callback_occluded( pbvh, [&](PBVHNode &node) { return ray_aabb_intersect(node, rcd); }, hit_fn); } bool ray_face_intersection_quad(const float ray_start[3], - IsectRayPrecalc *isect_precalc, + struct IsectRayPrecalc *isect_precalc, const float t0[3], const float t1[3], const float t2[3], @@ -1955,6 +2601,51 @@ bool ray_face_intersection_tri(const float ray_start[3], return false; } +static bool ray_update_depth_and_hit_count(const float depth_test, float *r_depth, int *hit_count) +{ + (*hit_count)++; + if (depth_test < *r_depth) { + *r_depth = depth_test; + return true; + } + + return false; +} + +static bool ray_face_intersection_depth_quad(const float ray_start[3], + struct IsectRayPrecalc *isect_precalc, + const float t0[3], + const float t1[3], + const float t2[3], + const float t3[3], + float *r_depth, + int *hit_count) +{ + float depth_test; + if (!(isect_ray_tri_watertight_v3(ray_start, isect_precalc, t0, t1, t2, &depth_test, nullptr) || + isect_ray_tri_watertight_v3(ray_start, isect_precalc, t0, t2, t3, &depth_test, nullptr))) + { + return false; + } + return ray_update_depth_and_hit_count(depth_test, r_depth, hit_count); +} + +bool ray_face_intersection_depth_tri(const float ray_start[3], + struct IsectRayPrecalc *isect_precalc, + const float t0[3], + const float t1[3], + const float t2[3], + float *r_depth, + int *hit_count) +{ + float depth_test; + + if (!isect_ray_tri_watertight_v3(ray_start, isect_precalc, t0, t1, t2, &depth_test, nullptr)) { + return false; + } + return ray_update_depth_and_hit_count(depth_test, r_depth, hit_count); +} + /* Take advantage of the fact we know this won't be an intersection. * Just handle ray-tri edges. */ static float dist_squared_ray_to_tri_v3_fast(const float ray_origin[3], @@ -2041,7 +2732,7 @@ static bool pbvh_faces_node_raycast(PBVH *pbvh, IsectRayPrecalc *isect_precalc, float *depth, PBVHVertRef *r_active_vertex, - int *r_active_face_index, + PBVHFaceRef *r_active_face_index, float *r_face_normal) { using namespace blender; @@ -2072,7 +2763,7 @@ static bool pbvh_faces_node_raycast(PBVH *pbvh, co[2] = positions[corner_verts[tri[2]]]; } - if (ray_face_intersection_tri(ray_start, isect_precalc, co[0], co[1], co[2], depth)) { + if (pbvh::ray_face_intersection_tri(ray_start, isect_precalc, co[0], co[1], co[2], depth)) { hit = true; if (r_face_normal) { @@ -2091,7 +2782,7 @@ static bool pbvh_faces_node_raycast(PBVH *pbvh, { copy_v3_v3(nearest_vertex_co, co[j]); r_active_vertex->i = corner_verts[tri[j]]; - *r_active_face_index = pbvh->corner_tri_faces[tri_i]; + r_active_face_index->i = pbvh->corner_tri_faces[tri_i]; } } } @@ -2109,7 +2800,7 @@ static bool pbvh_grids_node_raycast(PBVH *pbvh, IsectRayPrecalc *isect_precalc, float *depth, PBVHVertRef *r_active_vertex, - int *r_active_grid_index, + PBVHFaceRef *r_active_grid_index, float *r_face_normal) { const int totgrid = node->prim_indices.size(); @@ -2181,7 +2872,7 @@ static bool pbvh_grids_node_raycast(PBVH *pbvh, } } if (r_active_grid_index) { - *r_active_grid_index = grid_index; + r_active_grid_index->i = grid_index; } } } @@ -2195,7 +2886,8 @@ static bool pbvh_grids_node_raycast(PBVH *pbvh, return hit; } -bool raycast_node(PBVH *pbvh, +bool raycast_node(SculptSession *ss, + PBVH *pbvh, PBVHNode *node, float (*origco)[3], bool use_origco, @@ -2203,11 +2895,13 @@ bool raycast_node(PBVH *pbvh, const Span hide_poly, const float ray_start[3], const float ray_normal[3], - IsectRayPrecalc *isect_precalc, + struct IsectRayPrecalc *isect_precalc, + int *hit_count, float *depth, PBVHVertRef *active_vertex, - int *active_face_grid_index, - float *face_normal) + PBVHFaceRef *active_face_grid, + float *face_normal, + int stroke_id) { bool hit = false; @@ -2227,8 +2921,9 @@ bool raycast_node(PBVH *pbvh, isect_precalc, depth, active_vertex, - active_face_grid_index, + active_face_grid, face_normal); + break; case PBVH_GRIDS: hit |= pbvh_grids_node_raycast(pbvh, @@ -2239,19 +2934,23 @@ bool raycast_node(PBVH *pbvh, isect_precalc, depth, active_vertex, - active_face_grid_index, + active_face_grid, face_normal); break; case PBVH_BMESH: - BM_mesh_elem_index_ensure(pbvh->header.bm, BM_VERT); - hit = bmesh_node_raycast(node, - ray_start, - ray_normal, - isect_precalc, - depth, - use_origco, - active_vertex, - face_normal); + hit = pbvh_bmesh_node_raycast(ss, + pbvh, + node, + ray_start, + ray_normal, + isect_precalc, + hit_count, + depth, + use_origco, + active_vertex, + active_face_grid, + face_normal, + stroke_id); break; } @@ -2265,28 +2964,32 @@ void clip_ray_ortho( return; } float rootmin_start, rootmin_end; - Bounds bb_root; - float bb_center[3], bb_diff[3]; + float3 bb_min_root, bb_max_root, bb_center, bb_diff; IsectRayAABB_Precalc ray; float ray_normal_inv[3]; float offset = 1.0f + 1e-3f; const float offset_vec[3] = {1e-3f, 1e-3f, 1e-3f}; + Bounds bb; + if (original) { - bb_root = BKE_pbvh_node_get_original_BB(&pbvh->nodes.first()); + bb = BKE_pbvh_node_get_original_BB(&pbvh->nodes.first()); } else { - bb_root = BKE_pbvh_node_get_BB(&pbvh->nodes.first()); + bb = BKE_pbvh_node_get_BB(&pbvh->nodes.first()); } + bb_min_root = bb.min; + bb_max_root = bb.max; + /* Calc rough clipping to avoid overflow later. See #109555. */ float mat[3][3]; axis_dominant_v3_to_m3(mat, ray_normal); float a[3], b[3], min[3] = {FLT_MAX, FLT_MAX, FLT_MAX}, max[3] = {FLT_MIN, FLT_MIN, FLT_MIN}; /* Compute AABB bounds rotated along ray_normal.*/ - copy_v3_v3(a, bb_root.min); - copy_v3_v3(b, bb_root.max); + copy_v3_v3(a, bb_min_root); + copy_v3_v3(b, bb_max_root); mul_m3_v3(mat, a); mul_m3_v3(mat, b); minmax_v3v3_v3(min, max, a); @@ -2295,7 +2998,7 @@ void clip_ray_ortho( float cent[3]; /* Find midpoint of aabb on ray. */ - mid_v3_v3v3(cent, bb_root.min, bb_root.max); + mid_v3_v3v3(cent, bb_min_root, bb_max_root); float t = line_point_factor_v3(cent, ray_start, ray_end); interp_v3_v3v3(cent, ray_start, ray_end, t); @@ -2305,18 +3008,19 @@ void clip_ray_ortho( madd_v3_v3v3fl(ray_end, cent, ray_normal, dist); /* Slightly offset min and max in case we have a zero width node - * (due to a plane mesh for instance), or faces very close to the bounding box boundary. */ - mid_v3_v3v3(bb_center, bb_root.max, bb_root.min); + * (due to a plane mesh for instance), or faces very close to the bounding box + * boundary. */ + mid_v3_v3v3(bb_center, bb_max_root, bb_min_root); /* Diff should be same for both min/max since it's calculated from center. */ - sub_v3_v3v3(bb_diff, bb_root.max, bb_center); + sub_v3_v3v3(bb_diff, bb_max_root, bb_center); /* Handles case of zero width bb. */ add_v3_v3(bb_diff, offset_vec); - madd_v3_v3v3fl(bb_root.max, bb_center, bb_diff, offset); - madd_v3_v3v3fl(bb_root.min, bb_center, bb_diff, -offset); + madd_v3_v3v3fl(bb_max_root, bb_center, bb_diff, offset); + madd_v3_v3v3fl(bb_min_root, bb_center, bb_diff, -offset); /* Final projection of start ray. */ isect_ray_aabb_v3_precalc(&ray, ray_start, ray_normal); - if (!isect_ray_aabb_v3(&ray, bb_root.min, bb_root.max, &rootmin_start)) { + if (!isect_ray_aabb_v3(&ray, bb_min_root, bb_max_root, &rootmin_start)) { return; } @@ -2324,7 +3028,7 @@ void clip_ray_ortho( mul_v3_v3fl(ray_normal_inv, ray_normal, -1.0); isect_ray_aabb_v3_precalc(&ray, ray_end, ray_normal_inv); /* Unlikely to fail exiting if entering succeeded, still keep this here. */ - if (!isect_ray_aabb_v3(&ray, bb_root.min, bb_root.max, &rootmin_end)) { + if (!isect_ray_aabb_v3(&ray, bb_min_root, bb_max_root, &rootmin_end)) { return; } @@ -2344,6 +3048,7 @@ void clip_ray_ortho( madd_v3_v3v3fl(ray_start, ray_start, ray_normal, rootmin_start); madd_v3_v3v3fl(ray_end, ray_end, ray_normal_inv, rootmin_end); } +} // namespace blender::bke::pbvh /* -------------------------------------------------------------------- */ @@ -2371,6 +3076,7 @@ static bool nearest_to_ray_aabb_dist_sq(PBVHNode *node, return depth > 0.0f; } +namespace blender::bke::pbvh { void find_nearest_to_ray(PBVH *pbvh, const FunctionRef fn, const float ray_start[3], @@ -2387,49 +3093,48 @@ void find_nearest_to_ray(PBVH *pbvh, }, fn); } +} // namespace blender::bke::pbvh static bool pbvh_faces_node_nearest_to_ray(PBVH *pbvh, const PBVHNode *node, float (*origco)[3], - const Span corner_verts, - const Span hide_poly, const float ray_start[3], const float ray_normal[3], float *depth, float *dist_sq) { - using namespace blender; const Span positions = pbvh->vert_positions; + const Span corner_verts = pbvh->corner_verts; bool hit = false; for (const int i : node->prim_indices.index_range()) { const int tri_i = node->prim_indices[i]; - const int3 &corner_tri = pbvh->corner_tris[tri_i]; + const int3 tri = pbvh->corner_tris[tri_i]; const int3 face_verts = node->face_vert_indices[i]; - if (!hide_poly.is_empty() && hide_poly[pbvh->corner_tri_faces[tri_i]]) { + if (pbvh->hide_poly && pbvh->hide_poly[pbvh->corner_tri_faces[tri_i]]) { continue; } if (origco) { /* Intersect with backed-up original coordinates. */ - hit |= ray_face_nearest_tri(ray_start, - ray_normal, - origco[face_verts[0]], - origco[face_verts[1]], - origco[face_verts[2]], - depth, - dist_sq); + hit |= pbvh::ray_face_nearest_tri(ray_start, + ray_normal, + origco[face_verts[0]], + origco[face_verts[1]], + origco[face_verts[2]], + depth, + dist_sq); } else { /* intersect with current coordinates */ - hit |= ray_face_nearest_tri(ray_start, - ray_normal, - positions[corner_verts[corner_tri[0]]], - positions[corner_verts[corner_tri[1]]], - positions[corner_verts[corner_tri[2]]], - depth, - dist_sq); + hit |= pbvh::ray_face_nearest_tri(ray_start, + ray_normal, + positions[corner_verts[tri[0]]], + positions[corner_verts[tri[1]]], + positions[corner_verts[tri[2]]], + depth, + dist_sq); } } @@ -2466,24 +3171,24 @@ static bool pbvh_grids_node_nearest_to_ray(PBVH *pbvh, } if (origco) { - hit |= ray_face_nearest_quad(ray_start, - ray_normal, - origco[y * gridsize + x], - origco[y * gridsize + x + 1], - origco[(y + 1) * gridsize + x + 1], - origco[(y + 1) * gridsize + x], - depth, - dist_sq); + hit |= pbvh::ray_face_nearest_quad(ray_start, + ray_normal, + origco[y * gridsize + x], + origco[y * gridsize + x + 1], + origco[(y + 1) * gridsize + x + 1], + origco[(y + 1) * gridsize + x], + depth, + dist_sq); } else { - hit |= ray_face_nearest_quad(ray_start, - ray_normal, - CCG_grid_elem_co(&pbvh->gridkey, grid, x, y), - CCG_grid_elem_co(&pbvh->gridkey, grid, x + 1, y), - CCG_grid_elem_co(&pbvh->gridkey, grid, x + 1, y + 1), - CCG_grid_elem_co(&pbvh->gridkey, grid, x, y + 1), - depth, - dist_sq); + hit |= pbvh::ray_face_nearest_quad(ray_start, + ray_normal, + CCG_grid_elem_co(&pbvh->gridkey, grid, x, y), + CCG_grid_elem_co(&pbvh->gridkey, grid, x + 1, y), + CCG_grid_elem_co(&pbvh->gridkey, grid, x + 1, y + 1), + CCG_grid_elem_co(&pbvh->gridkey, grid, x, y + 1), + depth, + dist_sq); } } } @@ -2496,16 +3201,17 @@ static bool pbvh_grids_node_nearest_to_ray(PBVH *pbvh, return hit; } -bool find_nearest_to_ray_node(PBVH *pbvh, +namespace blender::bke::pbvh { +bool find_nearest_to_ray_node(SculptSession *ss, + PBVH *pbvh, PBVHNode *node, float (*origco)[3], bool use_origco, - const Span corner_verts, - const Span hide_poly, const float ray_start[3], const float ray_normal[3], float *depth, - float *dist_sq) + float *dist_sq, + int stroke_id) { bool hit = false; @@ -2516,48 +3222,51 @@ bool find_nearest_to_ray_node(PBVH *pbvh, switch (pbvh->header.type) { case PBVH_FACES: hit |= pbvh_faces_node_nearest_to_ray( - pbvh, node, origco, corner_verts, hide_poly, ray_start, ray_normal, depth, dist_sq); + pbvh, node, origco, ray_start, ray_normal, depth, dist_sq); break; case PBVH_GRIDS: hit |= pbvh_grids_node_nearest_to_ray( pbvh, node, origco, ray_start, ray_normal, depth, dist_sq); break; case PBVH_BMESH: - hit = bmesh_node_nearest_to_ray(node, ray_start, ray_normal, depth, dist_sq, use_origco); + hit = pbvh_bmesh_node_nearest_to_ray( + ss, pbvh, node, ray_start, ray_normal, depth, dist_sq, use_origco, stroke_id); break; } return hit; } +} // namespace blender::bke::pbvh -enum PlaneAABBIsect { +typedef enum { ISECT_INSIDE, ISECT_OUTSIDE, ISECT_INTERSECT, -}; +} PlaneAABBIsect; /* Adapted from: * http://www.gamedev.net/community/forums/topic.asp?topic_id=512123 * Returns true if the AABB is at least partially within the frustum * (ok, not a real frustum), false otherwise. */ -static PlaneAABBIsect test_frustum_aabb(const Bounds &bounds, - const PBVHFrustumPlanes *frustum) +static PlaneAABBIsect test_frustum_aabb(const float bb_min[3], + const float bb_max[3], + PBVHFrustumPlanes *frustum) { PlaneAABBIsect ret = ISECT_INSIDE; - const float(*planes)[4] = frustum->planes; + float(*planes)[4] = frustum->planes; for (int i = 0; i < frustum->num_planes; i++) { float vmin[3], vmax[3]; for (int axis = 0; axis < 3; axis++) { if (planes[i][axis] < 0) { - vmin[axis] = bounds.min[axis]; - vmax[axis] = bounds.max[axis]; + vmin[axis] = bb_min[axis]; + vmax[axis] = bb_max[axis]; } else { - vmin[axis] = bounds.max[axis]; - vmax[axis] = bounds.min[axis]; + vmin[axis] = bb_max[axis]; + vmax[axis] = bb_min[axis]; } } @@ -2572,211 +3281,141 @@ static PlaneAABBIsect test_frustum_aabb(const Bounds &bounds, return ret; } -} // namespace blender::bke::pbvh - -bool BKE_pbvh_node_frustum_contain_AABB(const PBVHNode *node, const PBVHFrustumPlanes *data) +bool BKE_pbvh_node_frustum_contain_AABB(PBVHNode *node, PBVHFrustumPlanes *data) { - return blender::bke::pbvh::test_frustum_aabb(node->vb, data) != - blender::bke::pbvh::ISECT_OUTSIDE; + const float *bb_min, *bb_max; + /* BKE_pbvh_node_get_BB */ + bb_min = node->vb.min; + bb_max = node->vb.max; + + return test_frustum_aabb(bb_min, bb_max, data) != ISECT_OUTSIDE; } -bool BKE_pbvh_node_frustum_exclude_AABB(const PBVHNode *node, const PBVHFrustumPlanes *data) +bool BKE_pbvh_node_frustum_exclude_AABB(PBVHNode *node, PBVHFrustumPlanes *data) { - return blender::bke::pbvh::test_frustum_aabb(node->vb, data) != blender::bke::pbvh::ISECT_INSIDE; -} + const float *bb_min, *bb_max; + /* BKE_pbvh_node_get_BB */ + bb_min = node->vb.min; + bb_max = node->vb.max; -static blender::draw::pbvh::PBVH_GPU_Args pbvh_draw_args_init(const Mesh &mesh, - PBVH &pbvh, - const PBVHNode &node) -{ - /* TODO: Use an explicit argument for the original mesh to avoid relying on #PBVH::mesh. */ - blender::draw::pbvh::PBVH_GPU_Args args{}; - - args.pbvh_type = pbvh.header.type; - - args.face_sets_color_default = pbvh.mesh ? pbvh.mesh->face_sets_color_default : - mesh.face_sets_color_default; - args.face_sets_color_seed = pbvh.mesh ? pbvh.mesh->face_sets_color_seed : - mesh.face_sets_color_seed; - - args.active_color = mesh.active_color_attribute; - args.render_color = mesh.default_color_attribute; - - switch (pbvh.header.type) { - case PBVH_FACES: - args.vert_data = &mesh.vert_data; - args.corner_data = &mesh.corner_data; - args.face_data = &mesh.face_data; - args.mesh = pbvh.mesh; - args.vert_positions = pbvh.vert_positions; - args.corner_verts = mesh.corner_verts(); - args.corner_edges = mesh.corner_edges(); - args.corner_tris = pbvh.corner_tris; - args.vert_normals = pbvh.vert_normals; - args.face_normals = pbvh.face_normals; - /* Retrieve data from the original mesh. Ideally that would be passed to this function to - * make it clearer when each is used. */ - args.hide_poly = *pbvh.mesh->attributes().lookup(".hide_poly", AttrDomain::Face); - - args.prim_indices = node.prim_indices; - args.tri_faces = mesh.corner_tri_faces(); - break; - case PBVH_GRIDS: - args.vert_data = &pbvh.mesh->vert_data; - args.corner_data = &pbvh.mesh->corner_data; - args.face_data = &pbvh.mesh->face_data; - args.ccg_key = pbvh.gridkey; - args.mesh = pbvh.mesh; - args.grid_indices = node.prim_indices; - args.subdiv_ccg = pbvh.subdiv_ccg; - args.grids = pbvh.subdiv_ccg->grids; - args.vert_normals = pbvh.vert_normals; - break; - case PBVH_BMESH: - args.bm = pbvh.header.bm; - args.vert_data = &args.bm->vdata; - args.corner_data = &args.bm->ldata; - args.face_data = &args.bm->pdata; - args.bm_faces = &node.bm_faces; - args.cd_mask_layer = CustomData_get_offset_named( - &pbvh.header.bm->vdata, CD_PROP_FLOAT, ".sculpt_mask"); - - break; - } - - return args; + return test_frustum_aabb(bb_min, bb_max, data) != ISECT_INSIDE; } namespace blender::bke::pbvh { - -static void node_update_draw_buffers(const Mesh &mesh, PBVH &pbvh, PBVHNode &node) +void update_normals(PBVH &pbvh, struct SubdivCCG *subdiv_ccg) { - /* Create and update draw buffers. The functions called here must not - * do any OpenGL calls. Flags are not cleared immediately, that happens - * after GPU_pbvh_buffer_flush() which does the final OpenGL calls. */ - if (node.flag & PBVH_RebuildDrawBuffers) { - const blender::draw::pbvh::PBVH_GPU_Args args = pbvh_draw_args_init(mesh, pbvh, node); - node.draw_batches = blender::draw::pbvh::node_create(args); - } + /* Update normals */ - if (node.flag & PBVH_UpdateDrawBuffers) { - node.debug_draw_gen++; - - if (node.draw_batches) { - const blender::draw::pbvh::PBVH_GPU_Args args = pbvh_draw_args_init(mesh, pbvh, node); - blender::draw::pbvh::node_update(node.draw_batches, args); + if (pbvh.header.type == PBVH_BMESH) { + for (int i = 0; i < pbvh.nodes.size(); i++) { + if (pbvh.nodes[i].flag & PBVH_Leaf) { + BKE_pbvh_bmesh_check_tris(&pbvh, &pbvh.nodes[i]); + } } } -} -void free_draw_buffers(PBVH & /*pbvh*/, PBVHNode *node) -{ - if (node->draw_batches) { - draw::pbvh::node_free(node->draw_batches); - node->draw_batches = nullptr; + Vector nodes = blender::bke::pbvh::search_gather( + &pbvh, [&](PBVHNode &node) { return update_search(&node, PBVH_UpdateNormals); }); + + if (pbvh.header.type == PBVH_BMESH) { + pbvh_bmesh_normals_update(&pbvh, nodes); } -} - -static void pbvh_update_draw_buffers(const Mesh &mesh, - PBVH &pbvh, - Span nodes, - int update_flag) -{ - if (pbvh.header.type == PBVH_BMESH && !pbvh.header.bm) { - /* BMesh hasn't been created yet */ - return; + else if (pbvh.header.type == PBVH_FACES) { + update_normals_faces(pbvh, nodes, *pbvh.mesh); } - - if ((update_flag & PBVH_RebuildDrawBuffers) || ELEM(pbvh.header.type, PBVH_GRIDS, PBVH_BMESH)) { - /* Free buffers uses OpenGL, so not in parallel. */ + else if (pbvh.header.type == PBVH_GRIDS) { + IndexMaskMemory memory; + const IndexMask faces_to_update = blender::bke::pbvh::nodes_to_face_selection_grids( + *subdiv_ccg, nodes, memory); + BKE_subdiv_ccg_update_normals(*subdiv_ccg, faces_to_update); for (PBVHNode *node : nodes) { - if (node->flag & PBVH_RebuildDrawBuffers) { - free_draw_buffers(pbvh, node); - } - else if ((node->flag & PBVH_UpdateDrawBuffers) && node->draw_batches) { - const draw::pbvh::PBVH_GPU_Args args = pbvh_draw_args_init(mesh, pbvh, *node); - draw::pbvh::update_pre(node->draw_batches, args); - } + node->flag &= ~PBVH_UpdateNormals; } } - - /* Parallel creation and update of draw buffers. */ - threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { - for (PBVHNode *node : nodes.slice(range)) { - node_update_draw_buffers(mesh, pbvh, *node); - } - }); - - /* Flush buffers uses OpenGL, so not in parallel. */ - for (PBVHNode *node : nodes) { - if (node->flag & PBVH_UpdateDrawBuffers) { - - if (node->draw_batches) { - draw::pbvh::node_gpu_flush(node->draw_batches); - } - } - - node->flag &= ~(PBVH_RebuildDrawBuffers | PBVH_UpdateDrawBuffers); - } } +} // namespace blender::bke::pbvh +/** + * PBVH drawing, updating draw buffers as needed and culling any nodes outside + * the specified frustum. + */ +typedef struct PBVHDrawSearchData { + PBVHFrustumPlanes *frustum; + int accum_update_flag; + PBVHAttrReq *attrs; + int attrs_num; +} PBVHDrawSearchData; + +static bool pbvh_draw_search(PBVHNode *node, PBVHDrawSearchData *data) +{ + if (data->frustum && !BKE_pbvh_node_frustum_contain_AABB(node, data->frustum)) { + return false; + } + + data->accum_update_flag |= node->flag; + return true; +} + +namespace blender::bke::pbvh { void draw_cb(const Mesh &mesh, PBVH *pbvh, bool update_only_visible, - const PBVHFrustumPlanes &update_frustum, - const PBVHFrustumPlanes &draw_frustum, + PBVHFrustumPlanes &update_frustum, + PBVHFrustumPlanes &draw_frustum, const FunctionRef draw_fn) { + Vector nodes; + int update_flag = 0; + pbvh->draw_cache_invalid = false; + /* Search for nodes that need updates. */ if (update_only_visible) { - int update_flag = 0; - Vector nodes = search_gather(pbvh, [&](PBVHNode &node) { - if (!BKE_pbvh_node_frustum_contain_AABB(&node, &update_frustum)) { - return false; - } - update_flag |= node.flag; - return true; - }); - if (update_flag & (PBVH_RebuildDrawBuffers | PBVH_UpdateDrawBuffers)) { - pbvh_update_draw_buffers(mesh, *pbvh, nodes, update_flag); - } + /* Get visible nodes with draw updates. */ + PBVHDrawSearchData data = {}; + data.frustum = &update_frustum; + data.accum_update_flag = 0; + nodes = blender::bke::pbvh::search_gather( + pbvh, [&](PBVHNode &node) { return pbvh_draw_search(&node, &data); }); + update_flag = data.accum_update_flag; } else { /* Get all nodes with draw updates, also those outside the view. */ - Vector nodes = search_gather(pbvh, [&](PBVHNode &node) { - return update_search(&node, PBVH_RebuildDrawBuffers | PBVH_UpdateDrawBuffers); - }); - pbvh_update_draw_buffers(mesh, *pbvh, nodes, PBVH_RebuildDrawBuffers | PBVH_UpdateDrawBuffers); + const int search_flag = PBVH_RebuildDrawBuffers | PBVH_UpdateDrawBuffers; + nodes = blender::bke::pbvh::search_gather( + pbvh, [&](PBVHNode &node) { return update_search(&node, search_flag); }); + update_flag = PBVH_RebuildDrawBuffers | PBVH_UpdateDrawBuffers; + } + + /* Update draw buffers. */ + if (!nodes.is_empty() && (update_flag & (PBVH_RebuildDrawBuffers | PBVH_UpdateDrawBuffers))) { + pbvh_update_draw_buffers(mesh, pbvh, nodes, update_flag); } /* Draw visible nodes. */ - Vector nodes = search_gather(pbvh, [&](PBVHNode &node) { - return BKE_pbvh_node_frustum_contain_AABB(&node, &draw_frustum); - }); + PBVHDrawSearchData draw_data = {}; + draw_data.frustum = &draw_frustum; + draw_data.accum_update_flag = 0; + nodes = blender::bke::pbvh::search_gather( + pbvh, [&](PBVHNode &node) { return pbvh_draw_search(&node, &draw_data); }); for (PBVHNode *node : nodes) { - if (node->flag & PBVH_FullyHidden) { - continue; + if (!(node->flag & PBVH_FullyHidden)) { + PBVH_GPU_Args args = pbvh_draw_args_init(mesh, *pbvh, *node); + draw_fn(node->draw_batches, args); } - if (!node->draw_batches) { - continue; - } - const draw::pbvh::PBVH_GPU_Args args = pbvh_draw_args_init(mesh, *pbvh, *node); - draw_fn(node->draw_batches, args); } } - } // namespace blender::bke::pbvh -void BKE_pbvh_draw_debug_cb(PBVH *pbvh, - void (*draw_fn)(PBVHNode *node, - void *user_data, - const float bmin[3], - const float bmax[3], - PBVHNodeFlags flag), - void *user_data) +ATTR_NO_OPT void BKE_pbvh_draw_debug_cb(PBVH *pbvh, + void (*draw_fn)(PBVHNode *node, + void *user_data, + const float bmin[3], + const float bmax[3], + PBVHNodeFlags flag), + void *user_data) { PBVHNodeFlags flag = PBVH_Leaf; @@ -2799,22 +3438,30 @@ void BKE_pbvh_draw_debug_cb(PBVH *pbvh, void BKE_pbvh_grids_update(PBVH *pbvh, const CCGKey *key) { pbvh->gridkey = *key; + if (pbvh->subdiv_ccg) { + pbvh->grid_hidden = &pbvh->subdiv_ccg->grid_hidden; + + pbvh->grids = pbvh->subdiv_ccg->grids.data(); + pbvh->grid_to_face_map = pbvh->subdiv_ccg->grid_to_face_map; + } } void BKE_pbvh_vert_coords_apply(PBVH *pbvh, const Span vert_positions) { - using namespace blender::bke::pbvh; - BLI_assert(vert_positions.size() == pbvh->totvert); + BLI_assert(vert_positions.size() == pbvh->verts_num); if (!pbvh->deformed) { if (!pbvh->vert_positions.is_empty()) { - /* When the PBVH is deformed, it creates a separate vertex position array that it owns - * directly. Conceptually these copies often aren't and often adds extra indirection, but: - * - Sculpting shape keys, the deformations are flushed back to the keys as a separate step. - * - Sculpting on a deformed mesh, deformations are also flushed to original positions - * separately. - * - The PBVH currently always assumes we want to change positions, and has no way to avoid - * calculating normals if it's only used for painting, for example. */ + /* When the PBVH is deformed, it creates a separate vertex position array that it + * owns directly. Conceptually these copies often aren't and often adds extra + * indirection, but: + * - Sculpting shape keys, the deformations are flushed back to the keys as a + * separate step. + * - Sculpting on a deformed mesh, deformations are also flushed to original + * positions separately. + * - The PBVH currently always assumes we want to change positions, and has no + * way to + * avoid calculating normals if it's only used for painting, for example. */ pbvh->vert_positions_deformed = pbvh->vert_positions.as_span(); pbvh->vert_positions = pbvh->vert_positions_deformed; @@ -2842,11 +3489,11 @@ void BKE_pbvh_vert_coords_apply(PBVH *pbvh, const Span vert_positions) BKE_pbvh_node_mark_update(&node); } - update_bounds(*pbvh, PBVH_UpdateBB | PBVH_UpdateOriginalBB); + blender::bke::pbvh::update_bounds(*pbvh, PBVH_UpdateBB | PBVH_UpdateOriginalBB); } } -bool BKE_pbvh_is_deformed(const PBVH *pbvh) +bool BKE_pbvh_is_deformed(PBVH *pbvh) { return pbvh->deformed; } @@ -2856,14 +3503,15 @@ PBVHProxyNode &BKE_pbvh_node_add_proxy(PBVH &pbvh, PBVHNode &node) { node.proxies.append_as(PBVHProxyNode{}); - /* It is fine to access pointer of the back element, since node is never handled from multiple - * threads, and the brush handler only requests a single proxy from the node, and never holds - * pointers to multiple proxies. */ + /* It is fine to access pointer of the back element, since node is never handled from + * multiple threads, and the brush handler only requests a single proxy from the + * node, and never holds pointers to multiple proxies. */ PBVHProxyNode &proxy_node = node.proxies.last(); const int num_unique_verts = BKE_pbvh_node_num_unique_verts(pbvh, node); - /* Brushes expect proxies to be zero-initialized, so that they can do additive operation to them. + /* Brushes expect proxies to be zero-initialized, so that they can do additive + * operation to them. */ proxy_node.co.resize(num_unique_verts, float3(0, 0, 0)); @@ -2875,25 +3523,6 @@ void BKE_pbvh_node_free_proxies(PBVHNode *node) node->proxies.clear_and_shrink(); } -PBVHColorBufferNode *BKE_pbvh_node_color_buffer_get(PBVHNode *node) -{ - - if (!node->color_buffer.color) { - node->color_buffer.color = static_cast( - MEM_callocN(sizeof(float[4]) * node->uniq_verts, "Color buffer")); - } - return &node->color_buffer; -} - -void BKE_pbvh_node_color_buffer_free(PBVH *pbvh) -{ - Vector nodes = blender::bke::pbvh::search_gather(pbvh, {}); - - for (PBVHNode *node : nodes) { - MEM_SAFE_FREE(node->color_buffer.color); - } -} - void pbvh_vertex_iter_init(PBVH *pbvh, PBVHNode *node, PBVHVertexIter *vi, int mode) { vi->grid = nullptr; @@ -2901,6 +3530,7 @@ void pbvh_vertex_iter_init(PBVH *pbvh, PBVHNode *node, PBVHVertexIter *vi, int m vi->fno = nullptr; vi->vert_positions = {}; vi->vertex.i = 0LL; + vi->index = 0; int uniq_verts; int totvert; @@ -2914,8 +3544,8 @@ void pbvh_vertex_iter_init(PBVH *pbvh, PBVHNode *node, PBVHVertexIter *vi, int m uniq_verts = node->uniq_verts; break; case PBVH_BMESH: - totvert = node->bm_unique_verts.size() + node->bm_other_verts.size(); - uniq_verts = node->bm_unique_verts.size(); + totvert = node->bm_unique_verts->size() + node->bm_other_verts->size(); + uniq_verts = node->bm_unique_verts->size(); break; } @@ -2945,28 +3575,39 @@ void pbvh_vertex_iter_init(PBVH *pbvh, PBVHNode *node, PBVHVertexIter *vi, int m vi->is_mesh = !pbvh->vert_positions.is_empty(); if (pbvh->header.type == PBVH_BMESH) { - vi->bm_unique_verts = node->bm_unique_verts.begin(); - vi->bm_unique_verts_end = node->bm_unique_verts.end(); - vi->bm_other_verts = node->bm_other_verts.begin(); - vi->bm_other_verts_end = node->bm_other_verts.end(); + if (mode == PBVH_ITER_ALL) { + pbvh_bmesh_check_other_verts(node); + } + + vi->vert_positions = {}; + + vi->bi = 0; + vi->bm_cur_set = 0; + vi->bm_unique_verts = node->bm_unique_verts; + vi->bm_other_verts = node->bm_other_verts; + vi->bm_iter = node->bm_unique_verts->begin(); + vi->bm_iter_end = node->bm_unique_verts->end(); + vi->bm_vdata = &pbvh->header.bm->vdata; + vi->bm_vert = nullptr; + vi->cd_vert_mask_offset = CustomData_get_offset_named( vi->bm_vdata, CD_PROP_FLOAT, ".sculpt_mask"); } - vi->gh.reset(); + vi->gh = BoundedBitSpan(); + if (vi->grids && mode == PBVH_ITER_UNIQUE) { - vi->grid_hidden = pbvh->subdiv_ccg->grid_hidden.is_empty() ? nullptr : - &pbvh->subdiv_ccg->grid_hidden; + vi->grid_hidden = pbvh->grid_hidden; } vi->mask = 0.0f; if (pbvh->header.type == PBVH_FACES) { vi->vert_normals = pbvh->vert_normals; - vi->hide_vert = static_cast( - CustomData_get_layer_named(&pbvh->mesh->vert_data, CD_PROP_BOOL, ".hide_vert")); + vi->hide_vert = pbvh->hide_vert; + vi->vmask = static_cast( - CustomData_get_layer_named(&pbvh->mesh->vert_data, CD_PROP_FLOAT, ".sculpt_mask")); + CustomData_get_layer_named(pbvh->vert_data, CD_PROP_FLOAT, ".sculpt_mask")); } } @@ -2992,13 +3633,14 @@ bool pbvh_has_face_sets(PBVH *pbvh) case PBVH_FACES: return pbvh->mesh->attributes().contains(".sculpt_face_set"); case PBVH_BMESH: - return CustomData_has_layer_named(&pbvh->header.bm->pdata, CD_PROP_FLOAT, ".sculpt_mask"); + return CustomData_get_offset_named( + &pbvh->header.bm->pdata, CD_PROP_INT32, ".sculpt_face_set") != -1; } + return false; } namespace blender::bke::pbvh { - void set_frustum_planes(PBVH *pbvh, PBVHFrustumPlanes *planes) { pbvh->num_planes = planes->num_planes; @@ -3007,28 +3649,30 @@ void set_frustum_planes(PBVH *pbvh, PBVHFrustumPlanes *planes) } } -void get_frustum_planes(const PBVH *pbvh, PBVHFrustumPlanes *planes) +void get_frustum_planes(PBVH *pbvh, PBVHFrustumPlanes *planes) { planes->num_planes = pbvh->num_planes; for (int i = 0; i < planes->num_planes; i++) { copy_v4_v4(planes->planes[i], pbvh->planes[i]); } } - } // namespace blender::bke::pbvh +#include "BKE_global.hh" +void BKE_pbvh_parallel_range_settings(TaskParallelSettings *settings, + bool use_threading, + int totnode) +{ + memset(settings, 0, sizeof(*settings)); + settings->use_threading = use_threading && totnode > 1 && G.debug_value != 890; +} + Mesh *BKE_pbvh_get_mesh(PBVH *pbvh) { return pbvh->mesh; } -Span BKE_pbvh_get_vert_positions(const PBVH *pbvh) -{ - BLI_assert(pbvh->header.type == PBVH_FACES); - return pbvh->vert_positions; -} - -MutableSpan BKE_pbvh_get_vert_positions(PBVH *pbvh) +MutableSpan BKE_pbvh_get_vert_positions(const PBVH *pbvh) { BLI_assert(pbvh->header.type == PBVH_FACES); return pbvh->vert_positions; @@ -3040,9 +3684,925 @@ Span BKE_pbvh_get_vert_normals(const PBVH *pbvh) return pbvh->vert_normals; } -void BKE_pbvh_subdiv_cgg_set(PBVH *pbvh, SubdivCCG *subdiv_ccg) +const bool *BKE_pbvh_get_vert_hide(const PBVH *pbvh) +{ + BLI_assert(pbvh->header.type == PBVH_FACES); + return pbvh->hide_vert; +} + +const bool *BKE_pbvh_get_poly_hide(const PBVH *pbvh) +{ + BLI_assert(ELEM(pbvh->header.type, PBVH_FACES, PBVH_GRIDS)); + return pbvh->hide_poly; +} + +bool *BKE_pbvh_get_vert_hide_for_write(PBVH *pbvh) +{ + BLI_assert(pbvh->header.type == PBVH_FACES); + if (pbvh->hide_vert) { + return pbvh->hide_vert; + } + pbvh->hide_vert = static_cast(CustomData_get_layer_named_for_write( + &pbvh->mesh->vert_data, CD_PROP_BOOL, ".hide_vert", pbvh->mesh->verts_num)); + if (pbvh->hide_vert) { + return pbvh->hide_vert; + } + pbvh->hide_vert = static_cast(CustomData_add_layer_named( + &pbvh->mesh->vert_data, CD_PROP_BOOL, CD_SET_DEFAULT, pbvh->mesh->verts_num, ".hide_vert")); + return pbvh->hide_vert; +} + +void BKE_pbvh_subdiv_ccg_set(PBVH *pbvh, SubdivCCG *subdiv_ccg) { pbvh->subdiv_ccg = subdiv_ccg; + pbvh->grid_to_face_map = subdiv_ccg->grid_to_face_map; + pbvh->grid_hidden = &subdiv_ccg->grid_hidden; + pbvh->grids = subdiv_ccg->grids.data(); +} + +void BKE_pbvh_update_hide_attributes_from_mesh(PBVH *pbvh) +{ + if (pbvh->header.type == PBVH_FACES) { + pbvh->hide_vert = static_cast(CustomData_get_layer_named_for_write( + &pbvh->mesh->vert_data, CD_PROP_BOOL, ".hide_vert", pbvh->mesh->verts_num)); + pbvh->hide_poly = static_cast(CustomData_get_layer_named_for_write( + &pbvh->mesh->face_data, CD_PROP_BOOL, ".hide_poly", pbvh->mesh->faces_num)); + } +} + +int BKE_pbvh_get_node_index(PBVH *pbvh, PBVHNode *node) +{ + return (int)(node - &pbvh->nodes[0]); +} + +int BKE_pbvh_get_totnodes(PBVH *pbvh) +{ + return pbvh->nodes.size(); +} + +int BKE_pbvh_get_node_id(PBVH * /*pbvh*/, PBVHNode *node) +{ + return node->id; +} + +PBVHNode *BKE_pbvh_node_from_index(PBVH *pbvh, int node_i) +{ + return &pbvh->nodes[node_i]; +} + +PBVHNode *BKE_pbvh_get_node(PBVH *pbvh, int node) +{ + return &pbvh->nodes[node]; +} + +void BKE_pbvh_vert_tag_update_normal_triangulation(PBVHNode *node) +{ + node->flag |= PBVH_UpdateTris; +} + +void BKE_pbvh_vert_tag_update_normal_tri_area(PBVHNode *node) +{ + node->flag |= PBVH_UpdateTriAreas; +} + +/* must be called outside of threads */ +void BKE_pbvh_face_areas_begin(Object *ob, PBVH *pbvh) +{ + Mesh *mesh = static_cast(ob->data); + + if (pbvh->header.type == PBVH_FACES) { + pbvh->corner_tri_faces = mesh->corner_tri_faces(); + } + BKE_pbvh_face_areas_swap_buffers(ob, pbvh); +} + +void BKE_pbvh_face_areas_swap_buffers(Object * /*ob*/, PBVH *pbvh) + +{ + pbvh->face_area_i ^= 1; +} + +void BKE_pbvh_update_all_tri_areas(PBVH *pbvh) +{ + /* swap read/write face area buffers */ + pbvh->face_area_i ^= 1; + + for (int i = 0; i < pbvh->nodes.size(); i++) { + PBVHNode *node = &pbvh->nodes[i]; + if (node->flag & PBVH_Leaf) { + node->flag |= PBVH_UpdateTriAreas; +#if 0 + // ensure node triangulations are valid + // so we don't end up doing it inside brush threads + BKE_pbvh_bmesh_check_tris(pbvh, node); +#endif + } + } +} + +void BKE_pbvh_check_tri_areas(PBVH *pbvh, PBVHNode *node) +{ + if (!(node->flag & PBVH_UpdateTriAreas)) { + return; + } + + if (pbvh->header.type == PBVH_BMESH) { + if (node->flag & PBVH_UpdateTris) { + BKE_pbvh_bmesh_check_tris(pbvh, node); + } + + if (!node->tribuf || !node->tribuf->tris.size()) { + return; + } + } + + node->flag &= ~PBVH_UpdateTriAreas; + + const int cur_i = pbvh->face_area_i ^ 1; + + switch (BKE_pbvh_type(pbvh)) { + case PBVH_FACES: { + for (int i = 0; i < (int)node->prim_indices.size(); i++) { + const int poly = pbvh->corner_tri_faces[node->prim_indices[i]]; + + if (pbvh->hide_poly && pbvh->hide_poly[poly]) { + /* Skip hidden faces. */ + continue; + } + + pbvh->face_areas[poly * 2 + cur_i] = 0.0f; + } + + for (int i = 0; i < (int)node->prim_indices.size(); i++) { + const int3 < = pbvh->corner_tris[node->prim_indices[i]]; + const int poly = pbvh->corner_tri_faces[node->prim_indices[i]]; + + if (pbvh->hide_poly && pbvh->hide_poly[poly]) { + /* Skip hidden faces. */ + continue; + } + + float area = area_tri_v3(pbvh->vert_positions[pbvh->corner_verts[lt[0]]], + pbvh->vert_positions[pbvh->corner_verts[lt[1]]], + pbvh->vert_positions[pbvh->corner_verts[lt[2]]]); + + pbvh->face_areas[poly * 2 + cur_i] += area; + + /* sanity check on read side of read write buffer */ + if (pbvh->face_areas[poly * 2 + (cur_i ^ 1)] == 0.0f) { + pbvh->face_areas[poly * 2 + (cur_i ^ 1)] = pbvh->face_areas[poly * 2 + cur_i]; + } + } + break; + } + case PBVH_GRIDS: + break; + case PBVH_BMESH: { + const int cd_face_area = pbvh->cd_face_area; + + for (BMFace *f : *node->bm_faces) { + float *areabuf = (float *)BM_ELEM_CD_GET_VOID_P(f, cd_face_area); + areabuf[cur_i] = 0.0f; + } + + for (PBVHTri &tri : node->tribuf->tris) { + BMVert *v1 = (BMVert *)(node->tribuf->verts[tri.v[0]].i); + BMVert *v2 = (BMVert *)(node->tribuf->verts[tri.v[1]].i); + BMVert *v3 = (BMVert *)(node->tribuf->verts[tri.v[2]].i); + BMFace *f = (BMFace *)tri.f.i; + + float *areabuf = (float *)BM_ELEM_CD_GET_VOID_P(f, cd_face_area); + areabuf[cur_i] += area_tri_v3(v1->co, v2->co, v3->co); + } + + for (BMFace *f : *node->bm_faces) { + float *areabuf = (float *)BM_ELEM_CD_GET_VOID_P(f, cd_face_area); + + /* sanity check on read side of read write buffer */ + if (areabuf[cur_i ^ 1] == 0.0f) { + areabuf[cur_i ^ 1] = areabuf[cur_i]; + } + } + + break; + } + default: + break; + } +} + +static void pbvh_pmap_to_edges_add(PBVH * /*pbvh*/, + PBVHVertRef /*vertex*/, + int **r_edges, + int *r_edges_size, + bool *heap_alloc, + int e, + int p, + int *len, + int **r_polys) +{ + for (int i = 0; i < *len; i++) { + if ((*r_edges)[i] == e) { + if ((*r_polys)[i * 2 + 1] == -1) { + (*r_polys)[i * 2 + 1] = p; + } + return; + } + } + + if (*len >= *r_edges_size) { + int newsize = *len + ((*len) >> 1) + 1; + + int *r_edges_new = (int *)MEM_malloc_arrayN(newsize, sizeof(*r_edges_new), "r_edges_new"); + int *r_polys_new = (int *)MEM_malloc_arrayN(newsize * 2, sizeof(*r_polys_new), "r_polys_new"); + + memcpy((void *)r_edges_new, (void *)*r_edges, sizeof(int) * (*r_edges_size)); + memcpy((void *)r_polys_new, (void *)(*r_polys), sizeof(int) * 2 * (*r_edges_size)); + + *r_edges_size = newsize; + + if (*heap_alloc) { + MEM_freeN(*r_polys); + MEM_freeN(*r_edges); + } + + *r_edges = r_edges_new; + *r_polys = r_polys_new; + + *heap_alloc = true; + } + + (*r_polys)[*len * 2] = p; + (*r_polys)[*len * 2 + 1] = -1; + + (*r_edges)[*len] = e; + (*len)++; +} + +void BKE_pbvh_pmap_to_edges(PBVH *pbvh, + PBVHVertRef vertex, + int **r_edges, + int *r_edges_size, + bool *r_heap_alloc, + int **r_polys) +{ + Span map = pbvh->vert_to_face_map[vertex.i]; + int len = 0; + + for (int i : IndexRange(map.index_range())) { + int loopstart = pbvh->faces[map[i]].start(); + int loop_count = pbvh->faces[map[i]].size(); + + const Span corner_verts(&pbvh->corner_verts[loopstart], loop_count); + const Span corner_edges(&pbvh->corner_edges[loopstart], loop_count); + + if (pbvh->hide_poly && pbvh->hide_poly[map[i]]) { + /* Skip connectivity from hidden faces. */ + continue; + } + + for (int j : IndexRange(loop_count)) { + if (corner_verts[j] == vertex.i) { + pbvh_pmap_to_edges_add(pbvh, + vertex, + r_edges, + r_edges_size, + r_heap_alloc, + corner_edges[(j + loop_count - 1) % loop_count], + map[i], + &len, + r_polys); + pbvh_pmap_to_edges_add(pbvh, + vertex, + r_edges, + r_edges_size, + r_heap_alloc, + corner_edges[j], + map[i], + &len, + r_polys); + } + } + } + + *r_edges_size = len; +} + +void BKE_pbvh_get_vert_face_areas(PBVH *pbvh, PBVHVertRef vertex, float *r_areas, int valence) +{ + const int cur_i = pbvh->face_area_i; + + switch (BKE_pbvh_type(pbvh)) { + case PBVH_FACES: { + int *edges = (int *)BLI_array_alloca(edges, 16); + int *faces = (int *)BLI_array_alloca(faces, 32); + bool heap_alloc = false; + int len = 16; + + BKE_pbvh_pmap_to_edges(pbvh, vertex, &edges, &len, &heap_alloc, &faces); + len = std::min(len, valence); + + if (!pbvh->vert_to_edge_map.is_empty()) { + /* sort face references by vemap edge ordering */ + Span emap = pbvh->vert_to_edge_map[vertex.i]; + + int *faces_old = (int *)BLI_array_alloca(faces, len * 2); + memcpy((void *)faces_old, (void *)faces, sizeof(int) * len * 2); + + /* note that wire edges will break this, but + should only result in incorrect weights + and isn't worth fixing */ + + for (int i = 0; i < len; i++) { + for (int j = 0; j < len; j++) { + if (emap[i] == edges[j]) { + faces[i * 2] = faces_old[j * 2]; + faces[i * 2 + 1] = faces_old[j * 2 + 1]; + } + } + } + } + for (int i = 0; i < len; i++) { + r_areas[i] = pbvh->face_areas[faces[i * 2] * 2 + cur_i]; + + if (faces[i * 2 + 1] != -1) { + r_areas[i] += pbvh->face_areas[faces[i * 2 + 1] * 2 + cur_i]; + r_areas[i] *= 0.5f; + } + } + + if (heap_alloc) { + MEM_freeN(edges); + MEM_freeN(faces); + } + + break; + } + case PBVH_BMESH: { + BMVert *v = (BMVert *)vertex.i; + BMEdge *e = v->e; + + if (!e) { + for (int i = 0; i < valence; i++) { + r_areas[i] = 1.0f; + } + + return; + } + + const int cd_face_area = pbvh->cd_face_area; + int j = 0; + + do { + float w = 0.0f; + BMVert *v2 = BM_edge_other_vert(e, v); + + if (*BM_ELEM_CD_PTR(v2, pbvh->cd_flag) & SCULPTFLAG_VERT_FSET_HIDDEN) { + continue; + } + + if (!e->l) { + w = 0.0f; + } + else { + float *a1 = (float *)BM_ELEM_CD_GET_VOID_P(e->l->f, cd_face_area); + float *a2 = (float *)BM_ELEM_CD_GET_VOID_P(e->l->radial_next->f, cd_face_area); + + w += a1[cur_i] * 0.5f; + w += a2[cur_i] * 0.5f; + } + + if (j >= valence) { + printf("%s: error, corrupt edge cycle, valence was %d expected %d\n", + __func__, + j + 1, + valence); + uint8_t *flags = BM_ELEM_CD_PTR(v, pbvh->cd_flag); + *flags |= SCULPTFLAG_NEED_VALENCE; + break; + } + + r_areas[j++] = w; + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + for (; j < valence; j++) { + r_areas[j] = 1.0f; + } + + break; + } + + case PBVH_GRIDS: { /* estimate from edge lengths */ + int index = (int)vertex.i; + + const CCGKey *key = BKE_pbvh_get_grid_key(pbvh); + const int grid_index = index / key->grid_area; + const int vertex_index = index - grid_index * key->grid_area; + + SubdivCCGCoord coord = {}; + + coord.grid_index = grid_index; + coord.x = short(vertex_index % key->grid_size); + coord.y = short(vertex_index / key->grid_size); + + SubdivCCGNeighbors neighbors; + BKE_subdiv_ccg_neighbor_coords_get(*pbvh->subdiv_ccg, coord, false, neighbors); + + float *co1 = CCG_elem_co(key, CCG_elem_offset(key, pbvh->grids[grid_index], vertex_index)); + float totw = 0.0f; + int i = 0; + + for (i = 0; i < neighbors.size; i++) { + SubdivCCGCoord *coord2 = neighbors.coords + i; + + int vertex_index2 = int(coord2->y) * key->grid_size + int(coord2->x); + + float *co2 = CCG_elem_co( + key, CCG_elem_offset(key, pbvh->grids[coord2->grid_index], vertex_index2)); + float w = len_v3v3(co1, co2); + + r_areas[i] = w; + totw += w; + } + + if (neighbors.size != valence) { + printf( + "%s: error! neighbors.size was %d expected %d\n", __func__, neighbors.size, valence); + } + if (totw < 0.000001f) { + for (int i = 0; i < neighbors.size; i++) { + r_areas[i] = 1.0f; + } + } + + for (; i < valence; i++) { + r_areas[i] = 1.0f; + } + + break; + } + } +} + +void BKE_pbvh_set_stroke_id(PBVH *pbvh, int stroke_id) +{ + pbvh->stroke_id = stroke_id; +} + +static void pbvh_boundaries_flag_update(PBVH *pbvh) +{ + + if (pbvh->header.bm) { + BMVert *v; + BMIter iter; + + BM_ITER_MESH (v, &iter, pbvh->header.bm, BM_VERTS_OF_MESH) { + pbvh_boundary_update_bmesh(pbvh, v); + } + } + else { + int verts_num = pbvh->totvert; + + if (BKE_pbvh_type(pbvh) == PBVH_GRIDS) { + verts_num = BKE_pbvh_get_grid_num_verts(pbvh); + } + + for (int i = 0; i < verts_num; i++) { + pbvh->boundary_flags[i] |= SCULPT_BOUNDARY_NEEDS_UPDATE; + } + } +} + +void BKE_pbvh_set_symmetry(PBVH *pbvh, int symmetry) +{ + if (symmetry == pbvh->symmetry) { + return; + } + + pbvh->symmetry = symmetry; +} + +namespace blender::bke::pbvh { + +Span get_poly_normals(const PBVH *pbvh) +{ + BLI_assert(pbvh->header.type == PBVH_FACES); + return pbvh->face_normals; +} + +void on_stroke_start(PBVH *pbvh) +{ + /* Load current node bounds into original bounds at stroke start.*/ + for (int i : IndexRange(pbvh->nodes.size())) { + PBVHNode *node = &pbvh->nodes[i]; + + node->orig_vb = node->vb; + } +} + +void set_vert_boundary_map(PBVH *pbvh, blender::BitVector<> *vert_boundary_map) +{ + pbvh->vert_boundary_map = vert_boundary_map; +} + +void update_edge_boundary_grids(int /*edge*/, + Span /*edges*/, + OffsetIndices /*polys*/, + int * /*edge_boundary_flags*/, + const int * /*vert_boundary_flags*/, + const int * /*face_sets*/, + const bool * /*sharp_edge*/, + const bool * /*seam_edge*/, + const GroupedSpan & /*pmap*/, + const GroupedSpan & /*epmap*/, + const CustomData * /*ldata*/, + SubdivCCG * /*subdiv_ccg*/, + const CCGKey * /*key*/, + float /*sharp_angle_limit*/, + blender::Span /*corner_verts*/, + blender::Span /*corner_edges*/) +{ + // +} + +static void get_edge_polys(int edge, + const GroupedSpan &pmap, + const GroupedSpan &epmap, + Span edges, + OffsetIndices polys, + Span corner_edges, + int *r_poly1, + int *r_poly2) +{ + *r_poly1 = -1; + *r_poly2 = -1; + + if (!epmap.is_empty()) { + Span polys = epmap[edge]; + + if (polys.size() > 0) { + *r_poly1 = polys[0]; + } + if (polys.size() > 1) { + *r_poly1 = polys[1]; + } + } + else { + int v1 = edges[edge][0]; + + for (int poly : pmap[v1]) { + for (int loop : polys[poly]) { + if (corner_edges[loop] == edge) { + if (*r_poly1 == -1) { + *r_poly1 = poly; + } + else { + *r_poly2 = poly; + } + } + } + } + } +} + +void update_edge_boundary_faces(int edge, + Span vertex_positions, + Span /*vertex_normals*/, + Span edges, + OffsetIndices polys, + Span poly_normals, + int *edge_boundary_flags, + const int *vert_boundary_flags, + const int *face_sets, + const bool *sharp_edge, + const bool *seam_edge, + const GroupedSpan &pmap, + const GroupedSpan &epmap, + const CustomData * /*ldata*/, + float sharp_angle_limit, + blender::Span corner_verts, + blender::Span corner_edges) +{ + int oldflag = edge_boundary_flags[edge]; + bool update_uv = oldflag & SCULPT_BOUNDARY_UPDATE_UV; + bool update_sharp = oldflag & SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE; + int newflag = 0; + + if (update_sharp) { + int poly1 = -1, poly2 = -1; + + edge_boundary_flags[edge] &= ~SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE; + + get_edge_polys(edge, pmap, epmap, edges, polys, corner_edges, &poly1, &poly2); + if (poly1 != -1 && poly2 != -1 && + test_sharp_faces_mesh( + poly1, poly2, sharp_angle_limit, vertex_positions, polys, poly_normals, corner_verts)) + { + edge_boundary_flags[edge] |= SCULPT_BOUNDARY_SHARP_ANGLE; + } + else { + edge_boundary_flags[edge] &= ~SCULPT_BOUNDARY_SHARP_ANGLE; + } + } + + if (!update_uv) { + newflag |= oldflag & SCULPT_BOUNDARY_UV; + } + + if (!(oldflag & SCULPT_BOUNDARY_NEEDS_UPDATE)) { + return; + } + + /* Some boundary types require an edge->poly map to be fully accurate. */ + if (!epmap.is_empty()) { + if (face_sets) { + int fset = -1; + + for (int poly : epmap[edge]) { + if (fset == -1) { + fset = face_sets[poly]; + } + else if (face_sets[poly] != fset) { + newflag |= SCULPT_BOUNDARY_FACE_SET; + break; + } + } + } + newflag |= epmap[edge].size() == 1 ? SCULPT_BOUNDARY_MESH : 0; + } + else { /* No edge->poly map; approximate from vertices (will give artifacts on + corners). */ + int v1 = edges[edge][0]; + int v2 = edges[edge][1]; + + int a = vert_boundary_flags[v1] & + ~(SCULPT_BOUNDARY_UPDATE_UV | SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE); + int b = vert_boundary_flags[v2] & + ~(SCULPT_BOUNDARY_UPDATE_UV | SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE); + + newflag |= a & b; + } + + newflag |= sharp_edge && sharp_edge[edge] ? SCULPT_BOUNDARY_SHARP_MARK : 0; + newflag |= (seam_edge && seam_edge[edge]) ? SCULPT_BOUNDARY_SEAM : 0; + + edge_boundary_flags[edge] = newflag; +} + +void set_flags_valence(PBVH *pbvh, uint8_t *flags, int *valence) +{ + pbvh->sculpt_flags = flags; + pbvh->valence = valence; +} + +void set_original(PBVH *pbvh, Span origco, Span origno) +{ + pbvh->origco = origco; + pbvh->origno = origno; +} + +void update_vert_boundary_faces(int *boundary_flags, + const int *face_sets, + const bool *hide_poly, + const int2 * /*medge*/, + const int *corner_verts, + const int *corner_edges, + OffsetIndices polys, + const blender::GroupedSpan &pmap, + PBVHVertRef vertex, + const bool *sharp_edges, + const bool *seam_edges, + uint8_t *flags, + int * /*valence*/) +{ + Span vert_map = pmap[vertex.i]; + uint8_t *flag = flags + vertex.i; + + *flag &= ~SCULPTFLAG_VERT_FSET_HIDDEN; + + int last_fset = -1; + int last_fset2 = -1; + + int *boundary_flag = boundary_flags + vertex.i; + *boundary_flag = 0; + + int totsharp = 0, totseam = 0, totsharp_angle = 0; + int visible = false; + + for (int i : vert_map.index_range()) { + int f_i = vert_map[i]; + + IndexRange poly = polys[f_i]; + const int *mc = corner_verts + poly.start(); + const int loop_count = poly.size(); + const int loopstart = poly.start(); + + int j = 0; + + for (j = 0; j < loop_count; j++, mc++) { + if (*mc == (int)vertex.i) { + break; + } + } + + if (j < loop_count) { + int e_index = corner_edges[loopstart + j]; + + if (sharp_edges && sharp_edges[e_index]) { + *boundary_flag |= SCULPT_BOUNDARY_SHARP_MARK; + totsharp++; + } + + if (seam_edges && seam_edges[e_index]) { + *boundary_flag |= SCULPT_BOUNDARY_SEAM; + totseam++; + } + } + + int fset = face_sets ? abs(face_sets[f_i]) : 1; + + if (!hide_poly || !hide_poly[f_i]) { + visible = true; + } + + if (i > 0 && fset != last_fset) { + *boundary_flag |= SCULPT_BOUNDARY_FACE_SET; + + if (i > 1 && last_fset2 != last_fset && last_fset != -1 && last_fset2 != -1 && fset != -1 && + last_fset2 != fset) + { + *boundary_flag |= SCULPT_CORNER_FACE_SET; + } + } + + if (i > 0 && last_fset != fset) { + last_fset2 = last_fset; + } + + last_fset = fset; + } + + if (!visible) { + *flag |= SCULPTFLAG_VERT_FSET_HIDDEN; + } + + if (totsharp_angle > 2) { + *boundary_flag |= SCULPT_CORNER_SHARP_ANGLE; + } + + if (!ELEM(totsharp, 0, 2)) { + *boundary_flag |= SCULPT_CORNER_SHARP_MARK; + } + + if (totseam > 2) { + *boundary_flag |= SCULPT_CORNER_SEAM; + } +} + +static bool check_unique_face_set_in_base_mesh(const PBVH *pbvh, + int vertex, + bool *r_corner, + const int *face_sets) +{ + if (!face_sets) { + return true; + } + int fset1 = -1, fset2 = -1, fset3 = -1; + + for (int poly : pbvh->vert_to_face_map[vertex]) { + int fset = face_sets[poly]; + + if (fset1 == -1) { + fset1 = fset; + } + else if (fset2 == -1 && fset != fset1) { + fset2 = fset; + } + else if (fset3 == -1 && fset != fset1 && fset != fset2) { + fset3 = fset; + } + } + + *r_corner = fset3 != -1; + return fset2 == -1; +} + +static bool check_boundary_vertex_in_base_mesh(const PBVH *pbvh, int vert) +{ + return pbvh->vert_boundary_map ? (*pbvh->vert_boundary_map)[vert] : false; +} + +/** + * Checks if the face sets of the adjacent faces to the edge between \a v1 and \a v2 + * in the base mesh are equal. + */ +static bool check_unique_face_set_for_edge_in_base_mesh(const PBVH *pbvh, + int v1, + int v2, + const int *face_sets) +{ + if (!face_sets) { + return true; + } + + int p1 = -1, p2 = -1; + for (int poly : pbvh->vert_to_face_map[v1]) { + const IndexRange p = pbvh->faces[poly]; + + for (int l = 0; l < p.size(); l++) { + const int *corner_verts = &pbvh->corner_verts[p.start() + l]; + if (*corner_verts == v2) { + if (p1 == -1) { + p1 = poly; + break; + } + + if (p2 == -1) { + p2 = poly; + break; + } + } + } + } + + if (p1 != -1 && p2 != -1) { + return abs(face_sets[p1]) == (face_sets[p2]); + } + return true; +} + +void update_vert_boundary_grids(PBVH *pbvh, int index, const int *face_sets) +{ + + int *flag = pbvh->boundary_flags + index; + + *flag = 0; + + const CCGKey *key = BKE_pbvh_get_grid_key(pbvh); + const int grid_index = index / key->grid_area; + const int vertex_index = index - grid_index * key->grid_area; + SubdivCCGCoord coord{}; + + coord.grid_index = grid_index; + coord.x = vertex_index % key->grid_size; + coord.y = vertex_index / key->grid_size; + + int v1, v2; + const SubdivCCGAdjacencyType adjacency = BKE_subdiv_ccg_coarse_mesh_adjacency_info_get( + *pbvh->subdiv_ccg, coord, pbvh->corner_verts, pbvh->faces, v1, v2); + + bool fset_corner = false; + switch (adjacency) { + case SUBDIV_CCG_ADJACENT_VERTEX: + if (!check_unique_face_set_in_base_mesh(pbvh, v1, &fset_corner, face_sets)) { + *flag |= SCULPT_BOUNDARY_FACE_SET; + } + if (check_boundary_vertex_in_base_mesh(pbvh, v1)) { + *flag |= SCULPT_BOUNDARY_MESH; + } + break; + case SUBDIV_CCG_ADJACENT_EDGE: { + if (!check_unique_face_set_for_edge_in_base_mesh(pbvh, v1, v2, face_sets)) { + *flag |= SCULPT_BOUNDARY_FACE_SET; + } + + if (check_boundary_vertex_in_base_mesh(pbvh, v1) && + check_boundary_vertex_in_base_mesh(pbvh, v2)) + { + *flag |= SCULPT_BOUNDARY_MESH; + } + break; + } + case SUBDIV_CCG_ADJACENT_NONE: + break; + } + + if (fset_corner) { + *flag |= SCULPT_CORNER_FACE_SET | SCULPT_BOUNDARY_FACE_SET; + } +} + +} // namespace blender::bke::pbvh + +void BKE_pbvh_distort_correction_set(PBVH *pbvh, eAttrCorrectMode value) +{ + /* Condition to update UV boundaries.*/ + bool update = !pbvh->distort_correction_mode != !value; + pbvh->distort_correction_mode = value; + + if (update) { + pbvh_boundaries_flag_update(pbvh); + } +} + +void BKE_pbvh_set_bmesh(PBVH *pbvh, BMesh *bm) +{ + pbvh->header.bm = bm; +} + +BMLog *BKE_pbvh_get_bm_log(PBVH *pbvh) +{ + return pbvh->bm_log; } bool BKE_pbvh_is_drawing(const PBVH *pbvh) @@ -3050,6 +4610,11 @@ bool BKE_pbvh_is_drawing(const PBVH *pbvh) return pbvh->is_drawing; } +bool BKE_pbvh_draw_cache_invalid(const PBVH *pbvh) +{ + return pbvh->draw_cache_invalid; +} + void BKE_pbvh_is_drawing_set(PBVH *pbvh, bool val) { pbvh->is_drawing = val; @@ -3057,20 +4622,33 @@ void BKE_pbvh_is_drawing_set(PBVH *pbvh, bool val) void BKE_pbvh_update_active_vcol(PBVH *pbvh, Mesh *mesh) { - BKE_pbvh_get_color_layer(mesh, &pbvh->color_layer, &pbvh->color_domain); -} + CustomDataLayer *last_layer = pbvh->color_layer; -void BKE_pbvh_pmap_set(PBVH *pbvh, const blender::GroupedSpan vert_to_face_map) -{ - pbvh->vert_to_face_map = vert_to_face_map; + BKE_pbvh_get_color_layer(pbvh, mesh, &pbvh->color_layer, &pbvh->color_domain); + + if (pbvh->color_layer) { + pbvh->cd_vcol_offset = pbvh->color_layer->offset; + } + else { + pbvh->cd_vcol_offset = -1; + } + + if (pbvh->color_layer != last_layer) { + for (int i = 0; i < pbvh->nodes.size(); i++) { + PBVHNode *node = &pbvh->nodes[i]; + + if (node->flag & PBVH_Leaf) { + BKE_pbvh_node_mark_update_color(node); + } + } + } } void BKE_pbvh_ensure_node_loops(PBVH *pbvh) { - using namespace blender; BLI_assert(BKE_pbvh_type(pbvh) == PBVH_FACES); - int totloop = 0; + int corners_num = 0; /* Check if nodes already have loop indices. */ for (PBVHNode &node : pbvh->nodes) { @@ -3082,10 +4660,10 @@ void BKE_pbvh_ensure_node_loops(PBVH *pbvh) return; } - totloop += node.prim_indices.size() * 3; + corners_num += node.prim_indices.size() * 3; } - BLI_bitmap *visit = BLI_BITMAP_NEW(totloop, __func__); + BLI_bitmap *visit = BLI_BITMAP_NEW(corners_num, __func__); /* Create loop indices from node loop triangles. */ Vector corner_indices; @@ -3118,6 +4696,194 @@ int BKE_pbvh_debug_draw_gen_get(PBVHNode *node) return node->debug_draw_gen; } +void BKE_pbvh_set_boundary_flags(PBVH *pbvh, int *boundary_flags) +{ + pbvh->boundary_flags = boundary_flags; +} + +static void pbvh_face_iter_verts_reserve(PBVHFaceIter *fd, int verts_num) +{ + if (verts_num >= fd->verts_size_) { + fd->verts_size_ = (verts_num + 1) << 2; + + if (fd->verts != fd->verts_reserved_) { + MEM_SAFE_FREE(fd->verts); + } + + fd->verts = (PBVHVertRef *)MEM_malloc_arrayN(fd->verts_size_, sizeof(void *), __func__); + } + + fd->verts_num = verts_num; +} + +BLI_INLINE int face_iter_prim_to_face(PBVHFaceIter *fd, int prim_index) +{ + if (fd->subdiv_ccg_) { + return BKE_subdiv_ccg_grid_to_face_index(*fd->subdiv_ccg_, prim_index); + } + + return fd->looptri_faces_[prim_index]; +} + +static void pbvh_face_iter_step(PBVHFaceIter *fd, bool do_step) +{ + if (do_step) { + fd->i++; + } + + switch (fd->pbvh_type_) { + case PBVH_BMESH: { + if (do_step) { + ++fd->bm_iter_; + } + + if (fd->bm_iter_ == fd->bm_iter_end_) { + return; + } + + BMFace *f = *fd->bm_iter_; + fd->face.i = (intptr_t)f; + fd->index = f->head.index; + + if (fd->cd_face_set_ != -1) { + fd->face_set = (int *)BM_ELEM_CD_GET_VOID_P(f, fd->cd_face_set_); + } + + /* TODO: BMesh doesn't use .hide_poly yet.*/ + fd->hide = nullptr; + + pbvh_face_iter_verts_reserve(fd, f->len); + int vertex_i = 0; + + BMLoop *l = f->l_first; + do { + fd->verts[vertex_i++].i = (intptr_t)l->v; + } while ((l = l->next) != f->l_first); + + break; + } + case PBVH_GRIDS: + case PBVH_FACES: { + int face_i = 0; + + if (do_step) { + fd->prim_index_++; + + while (fd->prim_index_ < fd->node_->prim_indices.size()) { + face_i = face_iter_prim_to_face(fd, fd->node_->prim_indices[fd->prim_index_]); + + if (face_i != fd->last_face_index_) { + break; + } + + fd->prim_index_++; + } + } + else if (fd->prim_index_ < fd->node_->prim_indices.size()) { + face_i = face_iter_prim_to_face(fd, fd->node_->prim_indices[fd->prim_index_]); + } + + if (fd->prim_index_ >= fd->node_->prim_indices.size()) { + return; + } + + fd->last_face_index_ = face_i; + const int poly_start = fd->face_offsets_[face_i].start(); + const int poly_size = fd->face_offsets_[face_i].size(); + + fd->face.i = fd->index = face_i; + + if (fd->face_sets_) { + fd->face_set = fd->face_sets_ + face_i; + } + if (fd->hide_poly_) { + fd->hide = fd->hide_poly_ + face_i; + } + + pbvh_face_iter_verts_reserve(fd, poly_size); + + const int *face_verts = &fd->corner_verts_[poly_start]; + const int grid_area = fd->subdiv_key_.grid_area; + + for (int i = 0; i < poly_size; i++) { + if (fd->pbvh_type_ == PBVH_GRIDS) { + /* Grid corners. */ + fd->verts[i].i = (poly_start + i) * grid_area + grid_area - 1; + } + else { + fd->verts[i].i = face_verts[i]; + } + } + break; + } + } +} + +void BKE_pbvh_face_iter_step(PBVHFaceIter *fd) +{ + pbvh_face_iter_step(fd, true); +} + +void BKE_pbvh_face_iter_init(PBVH *pbvh, PBVHNode *node, PBVHFaceIter *fd) +{ + *fd = {}; + + fd->node_ = node; + fd->pbvh_type_ = BKE_pbvh_type(pbvh); + fd->verts = fd->verts_reserved_; + fd->verts_size_ = PBVH_FACE_ITER_VERTS_RESERVED; + + switch (BKE_pbvh_type(pbvh)) { + case PBVH_GRIDS: + fd->subdiv_ccg_ = pbvh->subdiv_ccg; + fd->subdiv_key_ = pbvh->gridkey; + ATTR_FALLTHROUGH; + case PBVH_FACES: + fd->face_offsets_ = pbvh->faces; + fd->corner_verts_ = pbvh->corner_verts; + fd->looptri_faces_ = pbvh->corner_tri_faces; + fd->hide_poly_ = pbvh->hide_poly; + fd->face_sets_ = static_cast(CustomData_get_layer_named_for_write( + pbvh->face_data, CD_PROP_INT32, ".sculpt_face_set", pbvh->faces_num)); + fd->last_face_index_ = -1; + + break; + case PBVH_BMESH: + fd->bm = pbvh->header.bm; + fd->cd_face_set_ = CustomData_get_offset_named( + &pbvh->header.bm->pdata, CD_PROP_INT32, ".sculpt_face_set"); + + fd->bm_iter_ = node->bm_faces->begin(); + fd->bm_iter_end_ = node->bm_faces->end(); + break; + } + + if (!BKE_pbvh_face_iter_done(fd)) { + pbvh_face_iter_step(fd, false); + } +} + +void BKE_pbvh_face_iter_finish(PBVHFaceIter *fd) +{ + if (fd->verts != fd->verts_reserved_) { + MEM_SAFE_FREE(fd->verts); + } +} + +bool BKE_pbvh_face_iter_done(PBVHFaceIter *fd) +{ + switch (fd->pbvh_type_) { + case PBVH_FACES: + case PBVH_GRIDS: + return fd->prim_index_ >= fd->node_->prim_indices.size(); + case PBVH_BMESH: + return fd->bm_iter_ == fd->bm_iter_end_; + default: + BLI_assert_unreachable(); + return true; + } +} + void BKE_pbvh_sync_visibility_from_verts(PBVH *pbvh, Mesh *mesh) { using namespace blender; @@ -3125,6 +4891,7 @@ void BKE_pbvh_sync_visibility_from_verts(PBVH *pbvh, Mesh *mesh) switch (pbvh->header.type) { case PBVH_FACES: { mesh_hide_vert_flush(*mesh); + BKE_pbvh_update_hide_attributes_from_mesh(pbvh); break; } case PBVH_BMESH: { @@ -3157,23 +4924,19 @@ void BKE_pbvh_sync_visibility_from_verts(PBVH *pbvh, Mesh *mesh) } case PBVH_GRIDS: { const OffsetIndices faces = mesh->faces(); - const BitGroupVector<> &grid_hidden = pbvh->subdiv_ccg->grid_hidden; CCGKey key = pbvh->gridkey; IndexMaskMemory memory; - const IndexMask hidden_faces = - grid_hidden.is_empty() ? - IndexMask::from_predicate(faces.index_range(), - GrainSize(1024), - memory, - [&](const int i) { - const IndexRange face = faces[i]; - return std::any_of( - face.begin(), face.end(), [&](const int corner) { - return grid_hidden[corner][key.grid_area - 1]; - }); - }) : - IndexMask(); + const IndexMask hidden_faces = IndexMask::from_predicate( + faces.index_range(), GrainSize(1024), memory, [&](const int i) { + const IndexRange face = faces[i]; + return std::any_of(face.begin(), face.end(), [&](const int corner) { + if ((*pbvh->grid_hidden)[corner].is_empty()) { + return false; + } + return (*pbvh->grid_hidden)[corner][key.grid_area - 1].test(); + }); + }); MutableAttributeAccessor attributes = mesh->attributes_for_write(); if (hidden_faces.is_empty()) { @@ -3188,6 +4951,55 @@ void BKE_pbvh_sync_visibility_from_verts(PBVH *pbvh, Mesh *mesh) } mesh_hide_face_flush(*mesh); + BKE_pbvh_update_hide_attributes_from_mesh(pbvh); + break; + } + } +} + +void BKE_pbvh_flush_tri_areas(Object *ob, PBVH *pbvh) +{ + BKE_pbvh_face_areas_begin(ob, pbvh); + + /* Preload face area back buffer. */ + for (int i : IndexRange(pbvh->nodes.size())) { + PBVHNode *node = &pbvh->nodes[i]; + + if (!(node->flag & PBVH_Leaf) || !(node->flag & PBVH_UpdateTriAreas)) { + continue; + } + + BKE_pbvh_check_tri_areas(pbvh, node); + } + + const int cur_i = pbvh->face_area_i ^ 1; + + switch (pbvh->header.type) { + case PBVH_BMESH: { + BMIter iter; + BMFace *f; + int cd_face_area = pbvh->cd_face_area; + if (cd_face_area == -1) { + return; + } + + BM_ITER_MESH (f, &iter, pbvh->header.bm, BM_FACES_OF_MESH) { + float *areas = BM_ELEM_CD_PTR(f, cd_face_area); + areas[cur_i ^ 1] = areas[cur_i]; + } + break; + } + case PBVH_FACES: + case PBVH_GRIDS: { + if (!pbvh->face_areas) { + return; + } + + int faces_num = BKE_pbvh_num_faces(pbvh); + + for (int i = 0; i < faces_num; i++) { + pbvh->face_areas[(i >> 1) + (cur_i ^ 1)] = pbvh->face_areas[(i >> 1) + cur_i]; + } break; } } @@ -3230,4 +5042,202 @@ Vector gather_proxies(PBVH *pbvh) return array; } + +Vector get_flagged_nodes(PBVH *pbvh, int flag) +{ + return blender::bke::pbvh::search_gather( + pbvh, [&](PBVHNode &node) { return update_search(&node, flag); }); +} + +struct GroupedSpan get_pmap(PBVH *pbvh) { + return pbvh->vert_to_face_map; +} + + void + set_pmap(PBVH *pbvh, GroupedSpan pmap) +{ + pbvh->vert_to_face_map = pmap; +} + +void set_vemap(PBVH *pbvh, GroupedSpan vemap) +{ + pbvh->vert_to_edge_map = vemap; +} + +static bool test_colinear_tri(int f, + Span positions, + blender::OffsetIndices polys, + Span corner_verts) +{ + Span verts = corner_verts.slice(polys[f]); + + float area_limit = 0.00001f; + area_limit = len_squared_v3v3(positions[verts[0]], positions[verts[1]]) * 0.001f; + + return area_tri_v3(positions[verts[0]], positions[verts[1]], positions[verts[2]]) <= area_limit; +} + +float test_sharp_faces_mesh(int f1, + int f2, + float limit, + Span positions, + blender::OffsetIndices &polys, + Span poly_normals, + Span corner_verts) +{ + float angle = math::safe_acos(dot_v3v3(poly_normals[f1], poly_normals[f2])); + + /* Detect coincident triangles. */ + if (polys[f1].size() == 3 && test_colinear_tri(f1, positions, polys, corner_verts)) { + return false; + } + if (polys[f2].size() == 3 && test_colinear_tri(f2, positions, polys, corner_verts)) { + return false; + } + + /* Try to ignore folded over edges. */ + if (angle > M_PI * 0.6) { + return false; + } + + return angle > limit; +} + +bool check_vert_boundary(PBVH *pbvh, PBVHVertRef vertex, const int *face_sets) +{ + switch (BKE_pbvh_type(pbvh)) { + case PBVH_BMESH: { + if (pbvh->cd_boundary_flag == -1) { + return false; + } + + return pbvh_check_vert_boundary_bmesh(pbvh, reinterpret_cast(vertex.i)); + } + case PBVH_FACES: { + if (!pbvh->boundary_flags) { + return false; + } + if (pbvh->boundary_flags[vertex.i] & + (SCULPT_BOUNDARY_NEEDS_UPDATE | SCULPT_BOUNDARY_UPDATE_UV)) + { + update_vert_boundary_faces(pbvh->boundary_flags, + face_sets, + pbvh->hide_poly, + nullptr, + pbvh->corner_verts.data(), + pbvh->corner_edges.data(), + pbvh->faces, + pbvh->vert_to_face_map, + vertex, + pbvh->sharp_edges, + pbvh->seam_edges, + pbvh->sculpt_flags, + pbvh->valence); + return true; + } + } + case PBVH_GRIDS: { + if (!pbvh->boundary_flags) { + return false; + } + + if (pbvh->boundary_flags[vertex.i] & + (SCULPT_BOUNDARY_NEEDS_UPDATE | SCULPT_BOUNDARY_UPDATE_UV)) + { + update_vert_boundary_grids(pbvh, vertex.i, face_sets); + return true; + } + } + } + + return false; +} + +bool check_edge_boundary(PBVH *pbvh, PBVHEdgeRef edge, const int *face_sets) +{ + switch (BKE_pbvh_type(pbvh)) { + case PBVH_BMESH: { + BMEdge *e = reinterpret_cast(edge.i); + + if (pbvh->cd_edge_boundary == -1) { + return false; + } + + if (BM_ELEM_CD_GET_INT(e, pbvh->cd_edge_boundary) & + (SCULPT_BOUNDARY_NEEDS_UPDATE | SCULPT_BOUNDARY_UPDATE_UV)) + { + update_edge_boundary_bmesh(e, + pbvh->cd_faceset_offset, + pbvh->cd_edge_boundary, + pbvh->cd_flag, + pbvh->cd_valence, + &pbvh->header.bm->ldata, + pbvh->sharp_angle_limit); + } + } + case PBVH_FACES: { + if (!pbvh->edge_boundary_flags) { + return false; + } + if (pbvh->edge_boundary_flags[edge.i] & + (SCULPT_BOUNDARY_NEEDS_UPDATE | SCULPT_BOUNDARY_UPDATE_UV)) + { + Span cos = pbvh->vert_positions; + Span nos = pbvh->vert_normals; + + update_edge_boundary_faces(edge.i, + cos, + nos, + pbvh->edges, + pbvh->faces, + pbvh->face_normals, + pbvh->edge_boundary_flags, + pbvh->boundary_flags, + face_sets, + pbvh->sharp_edges, + pbvh->seam_edges, + pbvh->vert_to_face_map, + {}, + pbvh->corner_data, + pbvh->sharp_angle_limit, + pbvh->corner_verts, + pbvh->corner_edges); + return true; + } + + break; + } + case PBVH_GRIDS: { + if (!pbvh->edge_boundary_flags) { + return false; + } + + if (pbvh->edge_boundary_flags[edge.i] & + (SCULPT_BOUNDARY_NEEDS_UPDATE | SCULPT_BOUNDARY_UPDATE_UV)) + { + update_edge_boundary_grids(edge.i, + pbvh->edges, + pbvh->faces, + pbvh->edge_boundary_flags, + pbvh->boundary_flags, + face_sets, + pbvh->sharp_edges, + pbvh->seam_edges, + pbvh->vert_to_face_map, + {}, + pbvh->corner_data, + pbvh->subdiv_ccg, + BKE_pbvh_get_grid_key(pbvh), + pbvh->sharp_angle_limit, + pbvh->corner_verts, + pbvh->corner_edges); + return true; + } + break; + } + } + + return false; +} + } // namespace blender::bke::pbvh diff --git a/source/blender/blenkernel/intern/pbvh_bmesh.cc b/source/blender/blenkernel/intern/pbvh_bmesh.cc index 9e8de909df1..a840c0805fe 100644 --- a/source/blender/blenkernel/intern/pbvh_bmesh.cc +++ b/source/blender/blenkernel/intern/pbvh_bmesh.cc @@ -6,234 +6,799 @@ * \ingroup bke */ +/* + +TODO: + +Convergence improvements: +1. DONE: Limit number of edges processed per run. +2. DONE: Scale split steps by ratio of long to short edges to + prevent runaway tesselation. +3. DONE: Detect and dissolve three and four valence vertices that are surrounded by + all tris. +4. DONE: Use different (coarser) brush spacing for applying dyntopo + +Drawing improvements: +4. PARTIAL DONE: Build and cache vertex index buffers, to reduce GPU bandwidth + +Topology rake: +5. DONE: Enable new curvature topology rake code and add to UI. +6. DONE: Add code to cache curvature data per vertex in a CD layer. + +*/ + #include "MEM_guardedalloc.h" -#include "BLI_bounds.hh" +#include "BLI_alloca.h" +#include "BLI_asan.h" +#include "BLI_buffer.h" #include "BLI_ghash.h" +#include "BLI_hash.h" #include "BLI_heap_simple.h" #include "BLI_math_geom.h" #include "BLI_math_vector.h" -#include "BLI_math_vector.hh" #include "BLI_memarena.h" +#include "BLI_rand.h" +#include "BLI_sort_utils.h" #include "BLI_span.hh" -#include "BLI_time.h" +#include "BLI_task.h" +#include "BLI_timeit.hh" #include "BLI_utildefines.h" +#include "BLI_bounds.hh" +#include "BLI_bounds_types.hh" +#include "BLI_index_range.hh" +#include "BLI_map.hh" +#include "BLI_math_vector.hh" +#include "BLI_math_vector_types.hh" +#include "BLI_set.hh" +#include "BLI_vector.hh" +#include "BLI_time.h" + +#include "atomic_ops.h" + +#include "DNA_material_types.h" +#include "DNA_mesh_types.h" + #include "BKE_DerivedMesh.hh" #include "BKE_ccg.h" +#include "BKE_context.hh" +#include "BKE_dyntopo.hh" +#include "BKE_dyntopo_set.hh" +#include "BKE_global.hh" +#include "BKE_paint.hh" #include "BKE_pbvh_api.hh" +#include "BKE_sculpt.hh" #include "DRW_pbvh.hh" +#include "atomic_ops.h" #include "bmesh.hh" +#include "bmesh_log.hh" +#include "dyntopo_intern.hh" #include "pbvh_intern.hh" -#include "CLG_log.h" +#include +#include +#include -static CLG_LogRef LOG = {"pbvh.bmesh"}; +using blender::Span; -/* Avoid skinny faces */ -#define USE_EDGEQUEUE_EVEN_SUBDIV -#ifdef USE_EDGEQUEUE_EVEN_SUBDIV -# include "BKE_global.hh" +#include + +using blender::Bounds; +using blender::float2; +using blender::float3; +using blender::IndexRange; +using blender::Map; +using blender::Set; +using blender::Vector; +using blender::bke::dyntopo::DyntopoSet; +using namespace blender; + +using blender::bke::AttrDomain; + +template T *c_array_from_vector(Vector &array) +{ + T *ret = MEM_cnew_array(array.size(), __func__); + memcpy(static_cast(ret), static_cast(array.data()), sizeof(T) * array.size()); + return ret; +} + +static void _debugprint(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); +} + +#ifdef PBVH_BMESH_DEBUG +void pbvh_bmesh_check_nodes_simple(PBVH *pbvh) +{ + for (int i = 0; i < pbvh->nodes.size(); i++) { + PBVHNode *node = &pbvh->nodes[i]; + BMFace *f; + + if (!(node->flag & PBVH_Leaf)) { + continue; + } + + for (BMFace *f : *node->bm_faces) { + if (!f || f->head.htype != BM_FACE) { + _debugprint("Corrupted (freed?) face in node->bm_faces\n"); + continue; + } + + if (BM_ELEM_CD_GET_INT(f, pbvh->cd_face_node_offset) != i) { + _debugprint("Face in more then one node\n"); + } + } + } +} + +ATTR_NO_OPT void pbvh_bmesh_check_nodes(PBVH *pbvh) +{ + BMVert *v; + BMIter iter; + + BM_ITER_MESH (v, &iter, pbvh->header.bm, BM_VERTS_OF_MESH) { + int ni = BM_ELEM_CD_GET_INT(v, pbvh->cd_vert_node_offset); + + if (ni >= 0 && (!v->e || !v->e->l)) { + //_debugprint("wire vert had node reference: %p (type %d)\n", v, v->head.htype); + // BM_ELEM_CD_SET_INT(v, pbvh->cd_vert_node_offset, DYNTOPO_NODE_NONE); + } + + if (ni < -1 || ni >= pbvh->nodes.size()) { + _debugprint("vert node ref was invalid: %p (type %d)\n", v, v->head.htype); + continue; + } + + if (ni == -1) { + continue; + } + + PBVHNode *node = &pbvh->nodes[ni]; + if (!(node->flag & PBVH_Leaf) || !node->bm_unique_verts) { + _debugprint("vert node ref was in non leaf node"); + continue; + } + + if (!node->bm_unique_verts->contains(v)) { + _debugprint("vert not in node->bm_unique_verts\n"); + } + + if (node->bm_other_verts->contains(v)) { + _debugprint("vert in node->bm_other_verts"); + } + + if (pbvh->cd_valence != -1) { + BKE_pbvh_bmesh_check_valence(pbvh, (PBVHVertRef){.i = (intptr_t)v}); + int valence = BM_ELEM_CD_GET_INT(v, pbvh->cd_valence); + + if (BM_vert_edge_count(v) != valence) { + _debugprint("cached vertex valence mismatch; old: %d, should be: %d\n", + valence, + BM_vert_edge_count(v)); + } + } + } + + for (int i = 0; i < pbvh->nodes.size(); i++) { + PBVHNode *node = &pbvh->nodes[i]; + BMVert *v; + BMFace *f; + + // delete nodes should + if (node->flag & PBVH_Delete) { + _debugprint("orphaned delete node\n"); + } + + if (!(node->flag & PBVH_Leaf)) { + if (node->bm_unique_verts || node->bm_other_verts || node->bm_faces) { + _debugprint("dangling leaf pointers in non-leaf node\n"); + } + + continue; + } + + for (BMVert *v : *node->bm_unique_verts) { + if (BM_elem_is_free((BMElem *)v, BM_VERT)) { + printf("bm_unique_verts has freed vertex.\n"); + continue; + } + + int ni = BM_ELEM_CD_GET_INT(v, pbvh->cd_vert_node_offset); + + if (ni != i) { + if (ni >= 0 && ni < pbvh->nodes.size()) { + PBVHNode *node2 = &pbvh->nodes[ni]; + _debugprint("v node offset is wrong, %d\n", + !node2->bm_unique_verts ? 0 : node2->bm_unique_verts->contains(v)); + } + else { + _debugprint("v node offset is wrong\n"); + } + } + + if (!v || v->head.htype != BM_VERT) { + _debugprint("corruption in pbvh! bm_unique_verts\n"); + } + else if (node->bm_other_verts->contains(v)) { + _debugprint("v in both unique and other verts\n"); + } + } + + for (BMFace *f : *node->bm_faces) { + if (!f || f->head.htype != BM_FACE) { + _debugprint("corruption in pbvh! bm_faces\n"); + continue; + } + + int ni = BM_ELEM_CD_GET_INT(f, pbvh->cd_face_node_offset); + if (&pbvh->nodes[ni] != node) { + _debugprint("face in multiple nodes!\n"); + } + } + + for (BMVert *v : *node->bm_other_verts) { + BMIter iter; + BMFace *f = nullptr; + + if (BM_elem_is_free((BMElem *)v, BM_VERT)) { + printf("bm_other_verts has freed vertex.\n"); + continue; + } + + int ni = int(node - pbvh->nodes); + + bool ok = false; + BM_ITER_ELEM (f, &iter, v, BM_FACES_OF_VERT) { + if (BM_ELEM_CD_GET_INT(f, pbvh->cd_face_node_offset) == ni) { + if (!node->bm_faces->contains(f)) { + _debugprint("Node does not contain f, but f has a node index to it\n"); + continue; + } + + ok = true; + break; + } + } + + if (!ok) { + int ni = BM_ELEM_CD_GET_INT(v, pbvh->cd_vert_node_offset); + _debugprint( + "v is in node.bm_other_verts but none of its faces are in node.bm_faces. owning node " + "(not this one): %d\n", + ni); + } + + if (!v || v->head.htype != BM_VERT) { + _debugprint("corruption in pbvh! bm_other_verts\n"); + } + else if (node->bm_unique_verts->contains(v)) { + _debugprint("v in both unique and other verts\n"); + } + } + } +} + +void BKE_pbvh_bmesh_check_nodes(PBVH *pbvh) +{ + pbvh_bmesh_check_nodes(pbvh); +} +#else +void BKE_pbvh_bmesh_check_nodes(PBVH * /*pbvh*/) {} #endif -namespace blender::bke::pbvh { +/** \} */ -/* Support for only operating on front-faces. */ -#define USE_EDGEQUEUE_FRONTFACE +/****************************** Vertex/Face APIs ******************************/ +namespace blender::bke::dyntopo { -/* Don't add edges into the queue multiple times. */ -#define USE_EDGEQUEUE_TAG -/** - * Ensure we don't have dirty tags for the edge queue, and that they are left cleared. - * (slow, even for debug mode, so leave disabled for now). - */ -#if defined(USE_EDGEQUEUE_TAG) && 0 -# if !defined(NDEBUG) -# define USE_EDGEQUEUE_TAG_VERIFY -# endif +void pbvh_kill_vert(PBVH *pbvh, BMVert *v, bool log_vert, bool log_edges) +{ + BMEdge *e = v->e; + + if (e && log_edges) { + do { + BM_log_edge_removed(pbvh->header.bm, pbvh->bm_log, e); + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + } + + /* Release IDs. */ + if (e) { + do { + BMLoop *l = e->l; + if (l) { + do { + int id = BM_idmap_get_id(pbvh->bm_idmap, reinterpret_cast(l->f)); + if (id != BM_ID_NONE) { + BM_idmap_release(pbvh->bm_idmap, l->f, true); + } + } while ((l = l->radial_next) != e->l); + } + + BM_idmap_release(pbvh->bm_idmap, e, true); + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + } + + if (log_vert) { + BM_log_vert_removed(pbvh->header.bm, pbvh->bm_log, v); + } + + BM_idmap_release(pbvh->bm_idmap, v, true); + BM_vert_kill(pbvh->header.bm, v); +} + +static BMVert *pbvh_bmesh_vert_create(PBVH *pbvh, + int node_index, + const float co[3], + const float no[3], + BMVert *v_example, + const int /*cd_vert_mask_offset*/) +{ + PBVHNode *node = &pbvh->nodes[node_index]; + + BLI_assert((pbvh->nodes.size() == 1 || node_index) && node_index <= pbvh->nodes.size()); + + /* avoid initializing customdata because its quite involved */ + BMVert *v = BM_vert_create(pbvh->header.bm, co, nullptr, BM_CREATE_NOP); + + pbvh_boundary_update_bmesh(pbvh, v); + dyntopo_add_flag(pbvh, v, SCULPTFLAG_NEED_VALENCE); + + if (v_example) { + v->head.hflag = v_example->head.hflag; + + CustomData_bmesh_copy_data( + &pbvh->header.bm->vdata, &pbvh->header.bm->vdata, v_example->head.data, &v->head.data); + + /* This value is logged below */ + copy_v3_v3(v->no, no); + + // keep MSculptVert copied from v_example as-is + } + else { +#if 0 /* XXX: do we need to load original data here ? */ + MSculptVert *mv = BKE_PBVH_SCULPTVERT(pbvh->cd_sculpt_vert, v); + + copy_v3_v3(mv->origco, co); + copy_v3_v3(mv->origno, no); + mv->origmask = 0.0f; #endif -// #define USE_VERIFY + /* This value is logged below */ + copy_v3_v3(v->no, no); + } -#ifdef USE_VERIFY -static void pbvh_bmesh_verify(PBVH *pbvh); -#endif + node->bm_unique_verts->add(v); + BM_ELEM_CD_SET_INT(v, pbvh->cd_vert_node_offset, node_index); -/* -------------------------------------------------------------------- */ -/** \name BMesh Utility API - * - * Use some local functions which assume triangles. - * \{ */ + node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateBB | PBVH_UpdateTris; + + /* Log the new vertex */ + BM_log_vert_added(pbvh->header.bm, pbvh->bm_log, v); + v->head.index = pbvh->header.bm->totvert; // set provisional index + + return v; +} + +static BMFace *bmesh_face_create_edge_log(PBVH *pbvh, + BMVert *v_tri[3], + BMEdge *e_tri[3], + const BMFace *f_example) +{ + BMFace *f; + + if (!e_tri) { + BMEdge *e_tri2[3]; + + for (int i = 0; i < 3; i++) { + BMVert *v1 = v_tri[i]; + BMVert *v2 = v_tri[(i + 1) % 3]; + + BMEdge *e = BM_edge_exists(v1, v2); + + if (!e) { + e = BM_edge_create(pbvh->header.bm, v1, v2, nullptr, BM_CREATE_NOP); + BM_log_edge_added(pbvh->header.bm, pbvh->bm_log, e); + } + + e_tri2[i] = e; + } + + // f = BM_face_create_verts(pbvh->header.bm, v_tri, 3, f_example, BM_CREATE_NOP, true); + f = BM_face_create(pbvh->header.bm, v_tri, e_tri2, 3, f_example, BM_CREATE_NOP); + } + else { + f = BM_face_create(pbvh->header.bm, v_tri, e_tri, 3, f_example, BM_CREATE_NOP); + } + + if (f_example) { + f->head.hflag = f_example->head.hflag; + } + + return f; +} /** - * Typically using BM_LOOPS_OF_VERT and BM_FACES_OF_VERT iterators are fine, - * however this is an area where performance matters so do it in-line. - * - * Take care since 'break' won't works as expected within these macros! + * \note Callers are responsible for checking if the face exists before adding. */ +BMFace *pbvh_bmesh_face_create(PBVH *pbvh, + int node_index, + BMVert *v_tri[3], + BMEdge *e_tri[3], + const BMFace *f_example, + bool ensure_verts, + bool log_face) +{ + PBVHNode *node = &pbvh->nodes[node_index]; -#define BM_LOOPS_OF_VERT_ITER_BEGIN(l_iter_radial_, v_) \ - { \ - struct { \ - BMVert *v; \ - BMEdge *e_iter, *e_first; \ - BMLoop *l_iter_radial; \ - } _iter; \ - _iter.v = v_; \ - if (_iter.v->e) { \ - _iter.e_iter = _iter.e_first = _iter.v->e; \ - do { \ - if (_iter.e_iter->l) { \ - _iter.l_iter_radial = _iter.e_iter->l; \ - do { \ - if (_iter.l_iter_radial->v == _iter.v) { \ - l_iter_radial_ = _iter.l_iter_radial; + /* ensure we never add existing face */ + BLI_assert(!BM_face_exists(v_tri, 3)); -#define BM_LOOPS_OF_VERT_ITER_END \ - } \ - } \ - while ((_iter.l_iter_radial = _iter.l_iter_radial->radial_next) != _iter.e_iter->l) \ - ; \ - } \ - } \ - while ((_iter.e_iter = BM_DISK_EDGE_NEXT(_iter.e_iter, _iter.v)) != _iter.e_first) \ - ; \ - } \ - } \ - ((void)0) + BMFace *f = bmesh_face_create_edge_log(pbvh, v_tri, e_tri, f_example); -#define BM_FACES_OF_VERT_ITER_BEGIN(f_iter_, v_) \ - { \ - BMLoop *l_iter_radial_; \ - BM_LOOPS_OF_VERT_ITER_BEGIN (l_iter_radial_, v_) { \ - f_iter_ = l_iter_radial_->f; + node->bm_faces->add(f); + BM_ELEM_CD_SET_INT(f, pbvh->cd_face_node_offset, node_index); -#define BM_FACES_OF_VERT_ITER_END \ - } \ - BM_LOOPS_OF_VERT_ITER_END; \ - } \ - ((void)0) + /* mark node for update */ + node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateNormals | PBVH_UpdateTris | + PBVH_UpdateCurvatureDir | PBVH_UpdateTriAreas; + node->flag &= ~PBVH_FullyHidden; + /* Log the new face */ + if (log_face) { + BM_log_face_added(pbvh->header.bm, pbvh->bm_log, f); + } + + int cd_vert_node = pbvh->cd_vert_node_offset; + + if (ensure_verts) { + BMLoop *l = f->l_first; + do { + int ni = BM_ELEM_CD_GET_INT(l->v, cd_vert_node); + + if (ni == DYNTOPO_NODE_NONE) { + node->bm_unique_verts->add(l->v); + BM_ELEM_CD_SET_INT(l->v, cd_vert_node, node_index); + + node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateBB | PBVH_UpdateTris; + } + + pbvh_boundary_update_bmesh(pbvh, l->v); + dyntopo_add_flag(pbvh, l->v, SCULPTFLAG_NEED_VALENCE); + + l = l->next; + } while (l != f->l_first); + } + else { + BMLoop *l = f->l_first; + do { + pbvh_boundary_update_bmesh(pbvh, l->v); + dyntopo_add_flag(pbvh, l->v, SCULPTFLAG_NEED_VALENCE); + } while ((l = l->next) != f->l_first); + } + + return f; +} + +BMVert *BKE_pbvh_vert_create_bmesh( + PBVH *pbvh, float co[3], float no[3], PBVHNode *node, BMVert *v_example) +{ + if (!node) { + for (int i = 0; i < pbvh->nodes.size(); i++) { + PBVHNode *node2 = &pbvh->nodes[i]; + + if (!(node2->flag & PBVH_Leaf)) { + continue; + } + + /* Ensure we have at least some node somewhere picked. */ + node = node2; + + bool ok = true; + + for (int j = 0; j < 3; j++) { + if (co[j] < node2->vb.min[j] || co[j] >= node2->vb.max[j]) { + continue; + } + } + + if (ok) { + break; + } + } + } + + BMVert *v; + + if (!node) { + printf("possible pbvh error\n"); + v = BM_vert_create(pbvh->header.bm, co, v_example, BM_CREATE_NOP); + BM_ELEM_CD_SET_INT(v, pbvh->cd_vert_node_offset, DYNTOPO_NODE_NONE); + + pbvh_boundary_update_bmesh(pbvh, v); + dyntopo_add_flag(pbvh, v, SCULPTFLAG_NEED_VALENCE); + +#if 0 /* XXX: do we need to load origco here? */ + copy_v3_v3(mv->origco, co); +#endif + + return v; + } + + return pbvh_bmesh_vert_create( + pbvh, int(node - &pbvh->nodes[0]), co, no, v_example, pbvh->cd_vert_mask_offset); +} + +PBVHNode *BKE_pbvh_node_from_face_bmesh(PBVH *pbvh, BMFace *f) +{ + return &pbvh->nodes[BM_ELEM_CD_GET_INT(f, pbvh->cd_face_node_offset)]; +} + +BMFace *BKE_pbvh_face_create_bmesh(PBVH *pbvh, + BMVert *v_tri[3], + BMEdge *e_tri[3], + const BMFace *f_example) +{ + int ni = DYNTOPO_NODE_NONE; + + for (int i = 0; i < 3; i++) { + BMVert *v = v_tri[i]; + BMLoop *l; + BMIter iter; + + BM_ITER_ELEM (l, &iter, v, BM_LOOPS_OF_VERT) { + int ni2 = BM_ELEM_CD_GET_INT(l->f, pbvh->cd_face_node_offset); + if (ni2 != DYNTOPO_NODE_NONE) { + ni = ni2; + break; + } + } + } + + if (ni == DYNTOPO_NODE_NONE) { + BMFace *f; + + /* No existing nodes? Find one. */ + for (int i = 0; i < pbvh->nodes.size(); i++) { + PBVHNode *node = &pbvh->nodes[i]; + + if (!(node->flag & PBVH_Leaf)) { + continue; + } + + for (int j = 0; j < 3; j++) { + BMVert *v = v_tri[j]; + + bool ok = true; + + for (int k = 0; k < 3; k++) { + if (v->co[k] < node->vb.min[k] || v->co[k] >= node->vb.max[k]) { + ok = false; + } + } + + if (ok && (ni == DYNTOPO_NODE_NONE || node->bm_faces->size() < pbvh->leaf_limit)) { + ni = i; + break; + } + } + + if (ni != DYNTOPO_NODE_NONE) { + break; + } + } + + if (ni == DYNTOPO_NODE_NONE) { + /* Empty pbvh? */ + f = bmesh_face_create_edge_log(pbvh, v_tri, e_tri, f_example); + + BM_ELEM_CD_SET_INT(f, pbvh->cd_face_node_offset, DYNTOPO_NODE_NONE); + + return f; + } + } + + return pbvh_bmesh_face_create(pbvh, ni, v_tri, e_tri, f_example, true, true); +} + +void pbvh_bmesh_vert_remove(PBVH *pbvh, BMVert *v) +{ + /* never match for first time */ + const int updateflag = PBVH_UpdateDrawBuffers | PBVH_UpdateBB | PBVH_UpdateTris | + PBVH_UpdateNormals; + + int ni = BM_ELEM_CD_GET_INT(v, pbvh->cd_vert_node_offset); + if (ni != DYNTOPO_NODE_NONE) { + PBVHNode *node = &pbvh->nodes[ni]; + node->bm_unique_verts->remove(v); + node->flag |= (PBVHNodeFlags)updateflag; + } + + BM_ELEM_CD_SET_INT(v, pbvh->cd_vert_node_offset, DYNTOPO_NODE_NONE); + + if (!v->e) { + return; + } + + const int tag = 2; + + BMEdge *e = v->e; + do { + BMLoop *l = e->l; + if (!l) { + continue; + } + + do { + l->f->head.api_flag |= tag; + } while ((l = l->radial_next) != e->l); + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + e = v->e; + do { + BMLoop *l = e->l; + if (!l) { + continue; + } + + do { + if (!(l->f->head.api_flag & tag)) { + continue; + } + + l->f->head.api_flag &= ~tag; + + int ni2 = BM_ELEM_CD_GET_INT(l->f, pbvh->cd_face_node_offset); + if (ni2 != DYNTOPO_NODE_NONE) { + PBVHNode *node = &pbvh->nodes[ni2]; + + if (ni2 >= 0 && ni2 < pbvh->nodes.size() && pbvh->nodes[ni2].flag & PBVH_Leaf) { + node->flag |= PBVHNodeFlags(updateflag); + node->bm_other_verts->remove(v); + } + } + } while ((l = l->radial_next) != e->l); + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); +} + +void pbvh_bmesh_face_remove( + PBVH *pbvh, BMFace *f, bool log_face, bool check_verts, bool /*ensure_ownership_transfer*/) +{ + PBVHNode *f_node = pbvh_bmesh_node_from_face(pbvh, f); + + if (!f_node || !(f_node->flag & PBVH_Leaf)) { + printf( + "%s: pbvh corruption. %d\n", __func__, BM_ELEM_CD_GET_INT(f, pbvh->cd_face_node_offset)); + return; + } + + /* Check if any of this face's vertices need to be removed + * from the node */ + if (check_verts) { + int ni = int(f_node - &pbvh->nodes[0]); + + BMLoop *l = f->l_first; + do { + bool owns_vert = BM_ELEM_CD_GET_INT(l->v, pbvh->cd_vert_node_offset) == ni; + bool ok = false; + int new_ni = DYNTOPO_NODE_NONE; + + BMIter iter; + BMLoop *l2; + BM_ITER_ELEM (l2, &iter, l->v, BM_LOOPS_OF_VERT) { + int ni2 = BM_ELEM_CD_GET_INT(l2->f, pbvh->cd_face_node_offset); + if (l2->f != f && ni2 == ni) { + ok = true; + } + + if (ni2 != DYNTOPO_NODE_NONE && ni2 != ni) { + if (ni2 < 0 || ni2 >= pbvh->nodes.size() || !(pbvh->nodes[ni2].bm_other_verts)) { + printf("error! invalid node index %d!\n", ni2); + } + else { + new_ni = ni2; + } + } + } + + if (!ok) { + if (owns_vert) { + f_node->bm_unique_verts->remove(l->v); + + if (new_ni != DYNTOPO_NODE_NONE) { + PBVHNode *new_node = &pbvh->nodes[new_ni]; + + new_node->bm_other_verts->remove(l->v); + new_node->bm_unique_verts->add(l->v); + BM_ELEM_CD_SET_INT(l->v, pbvh->cd_vert_node_offset, new_ni); + } + else { + BM_ELEM_CD_SET_INT(l->v, pbvh->cd_vert_node_offset, DYNTOPO_NODE_NONE); + } + } + else { + f_node->bm_other_verts->remove(l->v); + } + } + } while ((l = l->next) != f->l_first); + } + + /* Remove face from node and top level */ + f_node->bm_faces->remove(f); + BM_ELEM_CD_SET_INT(f, pbvh->cd_face_node_offset, DYNTOPO_NODE_NONE); + + /* Log removed face */ + if (log_face) { + BM_log_face_removed(pbvh->header.bm, pbvh->bm_log, f); + } + + /* mark node for update */ + f_node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateNormals | PBVH_UpdateTris | + PBVH_UpdateTriAreas | PBVH_UpdateCurvatureDir; +} +} // namespace blender::bke::dyntopo + +/****************************** Building ******************************/ + +/** Create invalid bounds for use with #math::min_max. */ static Bounds negative_bounds() { return {float3(std::numeric_limits::max()), float3(std::numeric_limits::lowest())}; } -static std::array bm_edges_from_tri(BMesh *bm, const Span v_tri) -{ - return { - BM_edge_create(bm, v_tri[0], v_tri[1], nullptr, BM_CREATE_NO_DOUBLE), - BM_edge_create(bm, v_tri[1], v_tri[2], nullptr, BM_CREATE_NO_DOUBLE), - BM_edge_create(bm, v_tri[2], v_tri[0], nullptr, BM_CREATE_NO_DOUBLE), - }; -} - -BLI_INLINE void bm_face_as_array_index_tri(BMFace *f, int r_index[3]) -{ - BMLoop *l = BM_FACE_FIRST_LOOP(f); - - BLI_assert(f->len == 3); - - r_index[0] = BM_elem_index_get(l->v); - l = l->next; - r_index[1] = BM_elem_index_get(l->v); - l = l->next; - r_index[2] = BM_elem_index_get(l->v); -} - -/** - * A version of #BM_face_exists, optimized for triangles - * when we know the loop and the opposite vertex. - * - * Check if any triangle is formed by (l_radial_first->v, l_radial_first->next->v, v_opposite), - * at either winding (since its a triangle no special checks are needed). - * - *
- * l_radial_first->v & l_radial_first->next->v
- * +---+
- * |  /
- * | /
- * + v_opposite
- * 
- * - * Its assumed that \a l_radial_first is never forming the target face. - */ -static BMFace *bm_face_exists_tri_from_loop_vert(BMLoop *l_radial_first, BMVert *v_opposite) -{ - BLI_assert( - !ELEM(v_opposite, l_radial_first->v, l_radial_first->next->v, l_radial_first->prev->v)); - if (l_radial_first->radial_next != l_radial_first) { - BMLoop *l_radial_iter = l_radial_first->radial_next; - do { - BLI_assert(l_radial_iter->f->len == 3); - if (l_radial_iter->prev->v == v_opposite) { - return l_radial_iter->f; - } - } while ((l_radial_iter = l_radial_iter->radial_next) != l_radial_first); - } - return nullptr; -} - -/** - * Uses a map of vertices to lookup the final target. - * References can't point to previous items (would cause infinite loop). - */ -static BMVert *bm_vert_hash_lookup_chain(GHash *deleted_verts, BMVert *v) -{ - while (true) { - BMVert **v_next_p = (BMVert **)BLI_ghash_lookup_p(deleted_verts, v); - if (v_next_p == nullptr) { - /* Not remapped. */ - return v; - } - if (*v_next_p == nullptr) { - /* Removed and not remapped. */ - return nullptr; - } - - /* Remapped. */ - v = *v_next_p; - } -} - -/** \} */ - -/****************************** Building ******************************/ - -/** Update node data after splitting. */ +/* Update node data after splitting */ static void pbvh_bmesh_node_finalize(PBVH *pbvh, const int node_index, const int cd_vert_node_offset, - const int cd_face_node_offset) + const int cd_face_node_offset, + bool add_orco) { PBVHNode *n = &pbvh->nodes[node_index]; bool has_visible = false; - n->vb = negative_bounds(); + n->draw_batches = nullptr; - for (BMFace *f : n->bm_faces) { - /* Update ownership of faces. */ + /* Create vert hash sets */ + if (!n->bm_unique_verts) { + n->bm_unique_verts = MEM_new>("bm_unique_verts"); + } + n->bm_other_verts = MEM_new>("bm_other_verts"); + + n->vb = negative_bounds(); + n->orig_vb = negative_bounds(); + + for (BMFace *f : *n->bm_faces) { + /* Update ownership of faces */ BM_ELEM_CD_SET_INT(f, cd_face_node_offset, node_index); - /* Update vertices. */ + /* Update vertices */ BMLoop *l_first = BM_FACE_FIRST_LOOP(f); BMLoop *l_iter = l_first; do { BMVert *v = l_iter->v; - if (!n->bm_unique_verts.contains(v)) { + + int *flags = BM_ELEM_CD_PTR(v, pbvh->cd_boundary_flag); + *flags |= SCULPT_BOUNDARY_NEEDS_UPDATE; + + if (!n->bm_unique_verts->contains(v)) { if (BM_ELEM_CD_GET_INT(v, cd_vert_node_offset) != DYNTOPO_NODE_NONE) { - n->bm_other_verts.add(v); + n->bm_other_verts->add(v); } else { - n->bm_unique_verts.add(v); + n->bm_unique_verts->add(v); BM_ELEM_CD_SET_INT(v, cd_vert_node_offset, node_index); } } - /* Update node bounding box. */ - math::min_max(float3(v->co), n->vb.min, n->vb.max); + + /* Update node bounding box */ + n->vb = bounds::expand(n->vb, float3(v->co)); + n->orig_vb = bounds::expand(n->orig_vb, float3(BM_ELEM_CD_PTR(v, pbvh->cd_origco))); } while ((l_iter = l_iter->next) != l_first); if (!BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { @@ -241,127 +806,229 @@ static void pbvh_bmesh_node_finalize(PBVH *pbvh, } } - BLI_assert(n->vb.min[0] <= n->vb.max[0] && n->vb.min[1] <= n->vb.max[1] && - n->vb.min[2] <= n->vb.max[2]); + BLI_assert(n->vb.bmin[0] <= n->vb.bmax[0] && n->vb.bmin[1] <= n->vb.bmax[1] && + n->vb.bmin[2] <= n->vb.bmax[2]); - n->orig_vb = n->vb; - - /* Build GPU buffers for new node and update vertex normals. */ + /* Build GPU buffers for new node and update vertex normals */ BKE_pbvh_node_mark_rebuild_draw(n); BKE_pbvh_node_fully_hidden_set(n, !has_visible); - n->flag |= PBVH_UpdateNormals; + n->flag |= PBVH_UpdateNormals | PBVH_UpdateCurvatureDir | PBVH_UpdateTris; + n->flag |= PBVH_UpdateBB | PBVH_UpdateOriginalBB; + + if (add_orco) { + BKE_pbvh_bmesh_check_tris(pbvh, n); + } } -/** Recursively split the node if it exceeds the leaf_limit. */ -static void pbvh_bmesh_node_split(PBVH *pbvh, - const Span> face_bounds, - int node_index) +static void pbvh_print_mem_size(PBVH *pbvh) +{ + BMesh *bm = pbvh->header.bm; + CustomData *cdatas[4] = {&bm->vdata, &bm->edata, &bm->ldata, &bm->pdata}; + + float memsize1[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + float memsize2[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + float tot = 0.0f; + + BLI_mempool *pools[4] = {bm->vpool, bm->epool, bm->lpool, bm->fpool}; + + for (int i = 0; i < 4; i++) { + CustomData *cdata = cdatas[i]; + + // memsize1[i] = (float)(sizes[i] * tots[i]) / 1024.0f / 1024.0f; + // memsize2[i] = (float)(cdata->totsize * tots[i]) / 1024.0f / 1024.0f; + memsize1[i] = float(BLI_mempool_get_size(pools[i])) / 1024.0f / 1024.0f; + memsize2[i] = cdata->pool ? float(BLI_mempool_get_size(cdata->pool)) / 1024.0 / 1024.0f : 0.0f; + + tot += memsize1[i] + memsize2[i]; + } + + printf("base sizes:\n"); + printf(" v: %.2fmb e: %.2fmb l: %.2fmb f: %.2fmb\n", + memsize1[0], + memsize1[1], + memsize1[2], + memsize1[3]); + + printf("custom attribute sizes:\n"); + printf(" v: %.2fmb e: %.2fmb l: %.2fmb f: %.2fmb\n", + memsize2[0], + memsize2[1], + memsize2[2], + memsize2[3]); + + int ptrsize = (int)sizeof(void *); + + float memsize3[3] = {(float)(ptrsize * pbvh->bm_idmap->map.size()) / 1024.0f / 1024.0f, + (float)(ptrsize * pbvh->bm_idmap->freelist.capacity()) / 1024.0f / 1024.0f, + pbvh->bm_idmap->free_idx_map ? + (float)(4 * pbvh->bm_idmap->free_idx_map->capacity()) / 1024.0f / + 1024.0f : + 0.0f}; + + printf("idmap sizes:\n map_size: %.2fmb freelist_len: %.2fmb free_ids_size: %.2fmb\n", + memsize3[0], + memsize3[1], + memsize3[2]); + + tot += memsize3[0] + memsize3[1] + memsize3[2]; + + printf("total: %.2f\n", tot); +} + +/* Recursively split the node if it exceeds the leaf_limit */ +static void pbvh_bmesh_node_split( + PBVH *pbvh, const BBC *bbc_array, int node_index, bool add_orco, int depth) { const int cd_vert_node_offset = pbvh->cd_vert_node_offset; const int cd_face_node_offset = pbvh->cd_face_node_offset; PBVHNode *n = &pbvh->nodes[node_index]; - if (n->bm_faces.size() <= pbvh->leaf_limit) { - /* Node limit not exceeded. */ - pbvh_bmesh_node_finalize(pbvh, node_index, cd_vert_node_offset, cd_face_node_offset); +#ifdef PROXY_ADVANCED + BKE_pbvh_free_proxyarray(pbvh, n); +#endif + + if (!n->bm_faces) { + printf("%s: n->bm_faces was nullptr! depth: %d\n", __func__, depth); return; } - /* Calculate bounding box around primitive centroids. */ - Bounds cb = negative_bounds(); - for (BMFace *f : n->bm_faces) { - const int i = BM_elem_index_get(f); - const float3 center = math::midpoint(face_bounds[i].min, face_bounds[i].max); - math::min_max(center, cb.min, cb.max); + if (n->depth >= PBVH_STACK_FIXED_DEPTH || n->bm_faces->size() <= pbvh->leaf_limit) { + /* Node limit not exceeded */ + pbvh_bmesh_node_finalize(pbvh, node_index, cd_vert_node_offset, cd_face_node_offset, add_orco); + return; } - /* Find widest axis and its midpoint. */ - const int axis = math::dominant_axis(cb.max - cb.min); - const float mid = math::midpoint(cb.max[axis], cb.min[axis]); + /* Calculate bounding box around primitive centroids */ + BB cb; + BB_reset(&cb); - /* Add two new child nodes. */ + for (BMFace *f : *n->bm_faces) { + const BBC *bbc = &bbc_array[BM_elem_index_get(f)]; + + BB_expand(&cb, bbc->bcentroid); + } + + /* Find widest axis and its midpoint */ + const int axis = BB_widest_axis(&cb); + const float mid = (cb.bmax[axis] + cb.bmin[axis]) * 0.5f; + + if (isnan(mid)) { + printf("NAN ERROR! %s\n", __func__); + } + + /* Add two new child nodes */ const int children = pbvh->nodes.size(); n->children_offset = children; pbvh->nodes.resize(pbvh->nodes.size() + 2); - /* Array reallocated, update current node pointer. */ + /* Array reallocated, update current node pointer */ n = &pbvh->nodes[node_index]; /* Initialize children */ PBVHNode *c1 = &pbvh->nodes[children], *c2 = &pbvh->nodes[children + 1]; + + c1->draw_batches = c2->draw_batches = nullptr; + c1->depth = c2->depth = n->depth + 1; + c1->flag |= PBVH_Leaf; c2->flag |= PBVH_Leaf; - c1->bm_faces.reserve(n->bm_faces.size() / 2); - c2->bm_faces.reserve(n->bm_faces.size() / 2); - /* Partition the parent node's faces between the two children. */ - for (BMFace *f : n->bm_faces) { - const int i = BM_elem_index_get(f); - if (math::midpoint(face_bounds[i].min[axis], face_bounds[i].max[axis]) < mid) { - c1->bm_faces.add(f); + c1->bm_faces = MEM_new>("bm_faces", int64_t(n->bm_faces->size() >> 1)); + c2->bm_faces = MEM_new>("bm_faces", int64_t(n->bm_faces->size() >> 1)); + + c1->bm_unique_verts = MEM_new>("bm_unique_verts"); + c2->bm_unique_verts = MEM_new>("bm_unique_verts"); + + c1->bm_other_verts = c2->bm_other_verts = nullptr; + + /* Partition the parent node's faces between the two children */ + for (BMFace *f : *n->bm_faces) { + const BBC *bbc = &bbc_array[BM_elem_index_get(f)]; + + if (bbc->bcentroid[axis] < mid) { + c1->bm_faces->add(f); } else { - c2->bm_faces.add(f); - } - } - - /* Enforce at least one primitive in each node */ - Set *empty = nullptr; - Set *other; - if (c1->bm_faces.is_empty()) { - empty = &c1->bm_faces; - other = &c2->bm_faces; - } - else if (c2->bm_faces.is_empty()) { - empty = &c2->bm_faces; - other = &c1->bm_faces; - } - if (empty) { - for (BMFace *f : *other) { - empty->add(f); - other->remove(f); - break; + c2->bm_faces->add(f); } } /* Clear this node */ - /* Mark this node's unique verts as unclaimed. */ - for (BMVert *v : n->bm_unique_verts) { - BM_ELEM_CD_SET_INT(v, cd_vert_node_offset, DYNTOPO_NODE_NONE); + /* Assign verts to c1 and c2. Note that the previous + method of simply marking them as untaken and rebuilding + unique verts later doesn't work, as it assumes that dyntopo + never assigns verts to nodes that don't contain their + faces.*/ + if (n->bm_unique_verts) { + for (BMVert *v : *n->bm_unique_verts) { + if (v->co[axis] < mid) { + BM_ELEM_CD_SET_INT(v, cd_vert_node_offset, (c1 - &pbvh->nodes[0])); + c1->bm_unique_verts->add(v); + } + else { + BM_ELEM_CD_SET_INT(v, cd_vert_node_offset, (c2 - &pbvh->nodes[0])); + c2->bm_unique_verts->add(v); + } + } + + MEM_delete(n->bm_unique_verts); } - /* Unclaim faces. */ - for (BMFace *f : n->bm_faces) { - BM_ELEM_CD_SET_INT(f, cd_face_node_offset, DYNTOPO_NODE_NONE); + if (n->bm_faces) { + /* Unclaim faces */ + for (BMFace *f : *n->bm_faces) { + BM_ELEM_CD_SET_INT(f, cd_face_node_offset, DYNTOPO_NODE_NONE); + } + + MEM_delete(n->bm_faces); } - n->bm_faces.clear_and_shrink(); + + if (n->bm_other_verts) { + MEM_delete(n->bm_other_verts); + } + + if (n->layer_disp) { + MEM_freeN(n->layer_disp); + } + + if (n->tribuf || n->tri_buffers) { + BKE_pbvh_bmesh_free_tris(pbvh, n); + } + + n->bm_faces = nullptr; + n->bm_unique_verts = nullptr; + n->bm_other_verts = nullptr; + n->layer_disp = nullptr; if (n->draw_batches) { draw::pbvh::node_free(n->draw_batches); + n->draw_batches = nullptr; } n->flag &= ~PBVH_Leaf; - /* Recurse. */ - pbvh_bmesh_node_split(pbvh, face_bounds, children); - pbvh_bmesh_node_split(pbvh, face_bounds, children + 1); + /* Recurse */ + pbvh_bmesh_node_split(pbvh, bbc_array, children, add_orco, depth + 1); + pbvh_bmesh_node_split(pbvh, bbc_array, children + 1, add_orco, depth + 1); /* Array maybe reallocated, update current node pointer */ n = &pbvh->nodes[node_index]; - /* Update bounding box. */ - n->vb = bounds::merge(pbvh->nodes[n->children_offset].vb, - pbvh->nodes[n->children_offset + 1].vb); + /* Update bounding box */ + n->vb = blender::bounds::merge(pbvh->nodes[n->children_offset].vb, + pbvh->nodes[n->children_offset + 1].vb); n->orig_vb = n->vb; } -/** Recursively split the node if it exceeds the leaf_limit. */ -static bool pbvh_bmesh_node_limit_ensure(PBVH *pbvh, int node_index) +/* Recursively split the node if it exceeds the leaf_limit */ +bool pbvh_bmesh_node_limit_ensure(PBVH *pbvh, int node_index) { - PBVHNode &node = pbvh->nodes[node_index]; - const int faces_num = node.bm_faces.size(); - if (faces_num <= pbvh->leaf_limit) { + DyntopoSet *bm_faces = pbvh->nodes[node_index].bm_faces; + const int bm_faces_size = bm_faces->size(); + + if (bm_faces_size <= pbvh->leaf_limit || pbvh->nodes[node_index].depth >= PBVH_STACK_FIXED_DEPTH) + { /* Node limit not exceeded */ return false; } @@ -369,20 +1036,23 @@ static bool pbvh_bmesh_node_limit_ensure(PBVH *pbvh, int node_index) /* Trigger draw manager cache invalidation. */ pbvh->draw_cache_invalid = true; - /* For each BMFace, store the AABB and AABB centroid. */ - Array> face_bounds(faces_num); + /* For each BMFace, store the AABB and AABB centroid */ + BBC *bbc_array = MEM_cnew_array(bm_faces_size, "BBC"); int i = 0; - for (BMFace *f : node.bm_faces) { - face_bounds[i] = negative_bounds(); + for (BMFace *f : *bm_faces) { + BBC *bbc = &bbc_array[i]; + + BB_reset((BB *)bbc); BMLoop *l_first = BM_FACE_FIRST_LOOP(f); BMLoop *l_iter = l_first; do { - math::min_max(float3(l_iter->v->co), face_bounds[i].min, face_bounds[i].max); + BB_expand((BB *)bbc, l_iter->v->co); } while ((l_iter = l_iter->next) != l_first); + BBC_update_centroid(bbc); - /* So we can do direct lookups on 'face_bounds'. */ + /* so we can do direct lookups on 'bbc_array' */ BM_elem_index_set(f, i); /* set_dirty! */ i++; } @@ -390,1448 +1060,417 @@ static bool pbvh_bmesh_node_limit_ensure(PBVH *pbvh, int node_index) /* Likely this is already dirty. */ pbvh->header.bm->elem_index_dirty |= BM_FACE; - pbvh_bmesh_node_split(pbvh, face_bounds, node_index); + pbvh_bmesh_node_split(pbvh, bbc_array, node_index, false, 0); + + MEM_freeN(bbc_array); + pbvh_bmesh_check_nodes(pbvh); return true; } /**********************************************************************/ -BLI_INLINE int pbvh_bmesh_node_index_from_vert(PBVH *pbvh, const BMVert *key) +static bool point_in_node(const PBVHNode *node, const float co[3]) { - const int node_index = BM_ELEM_CD_GET_INT((const BMElem *)key, pbvh->cd_vert_node_offset); - BLI_assert(node_index != DYNTOPO_NODE_NONE); - BLI_assert(node_index < pbvh->nodes.size()); - return node_index; + return co[0] >= node->vb.min[0] && co[0] <= node->vb.max[0] && co[1] >= node->vb.min[1] && + co[1] <= node->vb.max[1] && co[2] >= node->vb.min[2] && co[2] <= node->vb.max[2]; } -BLI_INLINE int pbvh_bmesh_node_index_from_face(PBVH *pbvh, const BMFace *key) +void bke_pbvh_insert_face_finalize(PBVH *pbvh, BMFace *f, const int ni) { - const int node_index = BM_ELEM_CD_GET_INT((const BMElem *)key, pbvh->cd_face_node_offset); - BLI_assert(node_index != DYNTOPO_NODE_NONE); - BLI_assert(node_index < pbvh->nodes.size()); - return node_index; + PBVHNode *node = &pbvh->nodes[ni]; + BM_ELEM_CD_SET_INT(f, pbvh->cd_face_node_offset, ni); + + if (!(node->flag & PBVH_Leaf)) { + printf("%s: major pbvh corruption error\n", __func__); + return; + } + + node->bm_faces->add(f); + + PBVHNodeFlags updateflag = PBVH_UpdateTris | PBVH_UpdateBB | PBVH_UpdateDrawBuffers | + PBVH_UpdateCurvatureDir | PBVH_UpdateColor | PBVH_UpdateMask | + PBVH_UpdateNormals | PBVH_UpdateOriginalBB | PBVH_UpdateVisibility | + PBVH_UpdateRedraw | PBVH_RebuildDrawBuffers | PBVH_UpdateTriAreas; + + node->flag |= updateflag; + + // ensure verts are in pbvh + BMLoop *l = f->l_first; + do { + int ni2 = BM_ELEM_CD_GET_INT(l->v, pbvh->cd_vert_node_offset); + + if (ni2 != DYNTOPO_NODE_NONE && + (ni2 < 0 || ni2 >= pbvh->nodes.size() || !(pbvh->nodes[ni2].flag & PBVH_Leaf))) + { + printf("%s: pbvh corruption\n", __func__); + ni2 = DYNTOPO_NODE_NONE; + } + + node->vb = bounds::expand(node->vb, float3(l->v->co)); + node->orig_vb = bounds::expand(node->orig_vb, + float3(BM_ELEM_CD_PTR(l->v, pbvh->cd_origco))); + + if (ni2 == DYNTOPO_NODE_NONE) { + node->bm_unique_verts->add(l->v); + BM_ELEM_CD_SET_INT(l->v, pbvh->cd_vert_node_offset, ni); + continue; + } + + PBVHNode *node2 = &pbvh->nodes[ni2]; + + if (ni2 != ni) { + node->bm_other_verts->add(l->v); + + node2->vb = bounds::expand(node2->vb, float3(l->v->co)); + node2->orig_vb = bounds::expand(node2->orig_vb, + *BM_ELEM_CD_PTR(l->v, pbvh->cd_origco)); + + node2->flag |= updateflag; + } + } while ((l = l->next) != f->l_first); } -BLI_INLINE PBVHNode *pbvh_bmesh_node_from_vert(PBVH *pbvh, const BMVert *key) +void bke_pbvh_insert_face(PBVH *pbvh, struct BMFace *f) { - return &pbvh->nodes[pbvh_bmesh_node_index_from_vert(pbvh, key)]; -} + int i = 0; + bool ok = false; + int ni = -1; -BLI_INLINE PBVHNode *pbvh_bmesh_node_from_face(PBVH *pbvh, const BMFace *key) -{ - return &pbvh->nodes[pbvh_bmesh_node_index_from_face(pbvh, key)]; -} + while (i < pbvh->nodes.size()) { + PBVHNode *node = &pbvh->nodes[i]; + bool ok2 = false; -static BMVert *pbvh_bmesh_vert_create(PBVH *pbvh, - const BMVert *v1, - const BMVert *v2, - const int node_index, - const float co[3], - const float no[3], - const int cd_vert_mask_offset) -{ - PBVHNode *node = &pbvh->nodes[node_index]; + if (node->flag & PBVH_Leaf) { + ok = true; + ni = i; + break; + } - BLI_assert((pbvh->nodes.size() == 1 || node_index) && node_index <= pbvh->nodes.size()); + if (node->children_offset == 0) { + continue; + } - /* Avoid initializing custom-data because its quite involved. */ - BMVert *v = BM_vert_create(pbvh->header.bm, co, nullptr, BM_CREATE_NOP); + for (int j = 0; j < 2; j++) { + int ni2 = node->children_offset + j; + if (ni2 == 0) { + continue; + } - BM_data_interp_from_verts(pbvh->header.bm, v1, v2, v, 0.5f); + PBVHNode *node2 = &pbvh->nodes[ni2]; + BMLoop *l = f->l_first; - /* This value is logged below. */ - copy_v3_v3(v->no, no); + do { + if (point_in_node(node2, l->v->co)) { + i = ni2; + ok2 = true; + break; + } - node->bm_unique_verts.add(v); - BM_ELEM_CD_SET_INT(v, pbvh->cd_vert_node_offset, node_index); + l = l->next; + } while (l != f->l_first); - node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateBB | PBVH_TopologyUpdated; + if (ok2) { + break; + } + } - /* Log the new vertex. */ - BM_log_vert_added(pbvh->bm_log, v, cd_vert_mask_offset); + if (!ok2) { + break; + } + } - return v; -} + if (!ok) { + // find closest node + float co[3]; + int tot = 0; + BMLoop *l = f->l_first; -/** - * \note Callers are responsible for checking if the face exists before adding. - */ -static BMFace *pbvh_bmesh_face_create(PBVH *pbvh, - int node_index, - const Span v_tri, - const Span e_tri, - const BMFace *f_example) -{ - PBVHNode *node = &pbvh->nodes[node_index]; + zero_v3(co); - /* Ensure we never add existing face. */ - BLI_assert(!BM_face_exists(v_tri.data(), 3)); + do { + add_v3_v3(co, l->v->co); + l = l->next; + tot++; + } while (l != f->l_first); - BMFace *f = BM_face_create( - pbvh->header.bm, v_tri.data(), e_tri.data(), 3, f_example, BM_CREATE_NOP); - f->head.hflag = f_example->head.hflag; + mul_v3_fl(co, 1.0f / (float)tot); + float mindis = 1e17; - node->bm_faces.add(f); - BM_ELEM_CD_SET_INT(f, pbvh->cd_face_node_offset, node_index); + for (int i = 0; i < pbvh->nodes.size(); i++) { + PBVHNode *node = &pbvh->nodes[i]; - node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateNormals | PBVH_TopologyUpdated; - node->flag &= ~PBVH_FullyHidden; + if (!(node->flag & PBVH_Leaf)) { + continue; + } - /* Log the new face. */ - BM_log_face_added(pbvh->bm_log, f); + float cent[3]; + add_v3_v3v3(cent, node->vb.min, node->vb.max); + mul_v3_fl(cent, 0.5f); - return f; -} - -#define pbvh_bmesh_node_vert_use_count_is_equal(pbvh, node, v, n) \ - (pbvh_bmesh_node_vert_use_count_at_most(pbvh, node, v, (n) + 1) == n) - -static int pbvh_bmesh_node_vert_use_count_at_most(PBVH *pbvh, - PBVHNode *node, - BMVert *v, - const int count_max) -{ - int count = 0; - BMFace *f; - - BM_FACES_OF_VERT_ITER_BEGIN (f, v) { - PBVHNode *f_node = pbvh_bmesh_node_from_face(pbvh, f); - if (f_node == node) { - count++; - if (count == count_max) { - return count; + float dis = len_squared_v3v3(co, cent); + if (dis < mindis) { + mindis = dis; + ni = i; } } } - BM_FACES_OF_VERT_ITER_END; - return count; + if (ni < 0 || !(pbvh->nodes[ni].flag & PBVH_Leaf)) { + fprintf(stderr, "pbvh error! failed to find node to insert face into!\n"); + fflush(stderr); + return; + } + + bke_pbvh_insert_face_finalize(pbvh, f, ni); } -/** Return a node that uses vertex `v` other than its current owner. */ -static PBVHNode *pbvh_bmesh_vert_other_node_find(PBVH *pbvh, BMVert *v) +static void pbvh_bmesh_regen_node_verts(PBVH *pbvh, PBVHNode *node, bool report) { - PBVHNode *current_node = pbvh_bmesh_node_from_vert(pbvh, v); - BMFace *f; + node->flag &= ~PBVH_RebuildNodeVerts; - BM_FACES_OF_VERT_ITER_BEGIN (f, v) { - PBVHNode *f_node = pbvh_bmesh_node_from_face(pbvh, f); + int usize = node->bm_unique_verts->size(); + int osize = node->bm_other_verts->size(); - if (f_node != current_node) { - return f_node; + DyntopoSet *old_unique_verts = node->bm_unique_verts; + DyntopoSet *old_other_verts = node->bm_other_verts; + + const int cd_vert_node = pbvh->cd_vert_node_offset; + const int ni = (int)(node - &pbvh->nodes[0]); + + auto check_vert = [&](BMVert *v) { + if (BM_elem_is_free(reinterpret_cast(v), BM_VERT)) { + if (report) { + printf("%s: corrupted vertex %p\n", __func__, v); + } + return; + } + int ni2 = BM_ELEM_CD_GET_INT(v, cd_vert_node); + + bool bad = ni2 == ni || ni2 < 0 || ni2 >= pbvh->nodes.size(); + bad = bad || pbvh->nodes[ni2].flag & PBVH_Delete; + bad = bad || !(pbvh->nodes[ni2].flag & PBVH_Leaf); + + if (bad) { + BM_ELEM_CD_SET_INT(v, cd_vert_node, DYNTOPO_NODE_NONE); + } + }; + + for (BMVert *v : *old_unique_verts) { + check_vert(v); + } + + for (BMVert *v : *old_other_verts) { + check_vert(v); + } + + node->bm_unique_verts = MEM_new>("bm_unique_verts"); + node->bm_other_verts = MEM_new>("bm_other_verts"); + + bool update = false; + + for (BMFace *f : *node->bm_faces) { + BMLoop *l = f->l_first; + do { + int ni2 = BM_ELEM_CD_GET_INT(l->v, cd_vert_node); + + if (ni2 == DYNTOPO_NODE_NONE) { + BM_ELEM_CD_SET_INT(l->v, cd_vert_node, ni); + ni2 = ni; + update = true; + } + + if (ni2 == ni) { + node->bm_unique_verts->add(l->v); + BM_ELEM_CD_SET_INT(l->v, cd_vert_node, ni); + } + else { + node->bm_other_verts->add(l->v); + } + } while ((l = l->next) != f->l_first); + } + + for (BMVert *v : *old_unique_verts) { + if (BM_elem_is_free(reinterpret_cast(v), BM_VERT)) { + if (report) { + printf("%s: corrupted vertex %p\n", __func__, v); + } + continue; + } + + if (BM_ELEM_CD_GET_INT(v, pbvh->cd_vert_node_offset) == -1) { + // try to find node to insert into + BMIter iter2; + BMFace *f2; + bool ok = false; + + BM_ITER_ELEM (f2, &iter2, v, BM_FACES_OF_VERT) { + int ni2 = BM_ELEM_CD_GET_INT(f2, pbvh->cd_face_node_offset); + + if (ni2 >= 0) { + PBVHNode *node2 = &pbvh->nodes[ni2]; + + node2->bm_unique_verts->add(v); + node2->bm_other_verts->remove(v); + + BM_ELEM_CD_SET_INT(v, pbvh->cd_vert_node_offset, ni2); + + ok = true; + break; + } + } + + if (!ok) { + printf("pbvh error: orphaned vert node reference\n"); + } + } + } + + if (usize != node->bm_unique_verts->size()) { + update = true; +#if 0 + printf("possible pbvh error: bm_unique_verts might have had bad data. old: %d, new: %d\n", + usize, + node->bm_unique_verts->size()); +#endif + } + + if (osize != node->bm_other_verts->size()) { + update = true; +#if 0 + printf("possible pbvh error: bm_other_verts might have had bad data. old: %d, new: %d\n", + osize, + node->bm_other_verts->size()); +#endif + } + + if (update) { + node->flag |= PBVH_UpdateNormals | PBVH_UpdateDrawBuffers | PBVH_RebuildDrawBuffers | + PBVH_UpdateBB; + node->flag |= PBVH_UpdateOriginalBB | PBVH_UpdateRedraw | PBVH_UpdateColor | PBVH_UpdateTris | + PBVH_UpdateVisibility; + } + + MEM_delete(old_unique_verts); + MEM_delete(old_other_verts); +} + +void BKE_pbvh_bmesh_mark_node_regen(PBVH * /*pbvh*/, PBVHNode *node) +{ + node->flag |= PBVH_RebuildNodeVerts; +} + +PBVHNode *BKE_pbvh_get_node_leaf_safe(PBVH *pbvh, int i) +{ + if (i >= 0 && i < pbvh->nodes.size()) { + PBVHNode *node = &pbvh->nodes[i]; + if ((node->flag & PBVH_Leaf) && !(node->flag & PBVH_Delete)) { + return node; } } - BM_FACES_OF_VERT_ITER_END; return nullptr; } -static void pbvh_bmesh_vert_ownership_transfer(PBVH *pbvh, PBVHNode *new_owner, BMVert *v) +void BKE_pbvh_bmesh_regen_node_verts(PBVH *pbvh, bool report) { - PBVHNode *current_owner = pbvh_bmesh_node_from_vert(pbvh, v); - current_owner->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateBB | PBVH_TopologyUpdated; + for (int i = 0; i < pbvh->nodes.size(); i++) { + PBVHNode *node = &pbvh->nodes[i]; - BLI_assert(current_owner != new_owner); - - /* Remove current ownership. */ - current_owner->bm_unique_verts.remove(v); - - /* Set new ownership */ - BM_ELEM_CD_SET_INT(v, pbvh->cd_vert_node_offset, new_owner - pbvh->nodes.data()); - new_owner->bm_unique_verts.add(v); - new_owner->bm_other_verts.remove(v); - BLI_assert(!new_owner->bm_other_verts.contains(v)); - - new_owner->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateBB | PBVH_TopologyUpdated; -} - -static void pbvh_bmesh_vert_remove(PBVH *pbvh, BMVert *v) -{ - /* Never match for first time. */ - int f_node_index_prev = DYNTOPO_NODE_NONE; - - PBVHNode *v_node = pbvh_bmesh_node_from_vert(pbvh, v); - v_node->bm_unique_verts.remove(v); - BM_ELEM_CD_SET_INT(v, pbvh->cd_vert_node_offset, DYNTOPO_NODE_NONE); - - /* Have to check each neighboring face's node. */ - BMFace *f; - BM_FACES_OF_VERT_ITER_BEGIN (f, v) { - const int f_node_index = pbvh_bmesh_node_index_from_face(pbvh, f); - - /* Faces often share the same node, quick check to avoid redundant #BLI_gset_remove calls. */ - if (f_node_index_prev != f_node_index) { - f_node_index_prev = f_node_index; - - PBVHNode *f_node = &pbvh->nodes[f_node_index]; - f_node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateBB | PBVH_TopologyUpdated; - - /* Remove current ownership. */ - f_node->bm_other_verts.remove(v); - - BLI_assert(!f_node->bm_unique_verts.contains(v)); - BLI_assert(!f_node->bm_other_verts.contains(v)); - } - } - BM_FACES_OF_VERT_ITER_END; -} - -static void pbvh_bmesh_face_remove(PBVH *pbvh, BMFace *f) -{ - PBVHNode *f_node = pbvh_bmesh_node_from_face(pbvh, f); - - /* Check if any of this face's vertices need to be removed from the node. */ - BMLoop *l_first = BM_FACE_FIRST_LOOP(f); - BMLoop *l_iter = l_first; - do { - BMVert *v = l_iter->v; - if (pbvh_bmesh_node_vert_use_count_is_equal(pbvh, f_node, v, 1)) { - if (f_node->bm_unique_verts.contains(v)) { - /* Find a different node that uses 'v'. */ - PBVHNode *new_node; - - new_node = pbvh_bmesh_vert_other_node_find(pbvh, v); - BLI_assert(new_node || BM_vert_face_count_is_equal(v, 1)); - - if (new_node) { - pbvh_bmesh_vert_ownership_transfer(pbvh, new_node, v); - } - } - else { - /* Remove from other verts. */ - f_node->bm_other_verts.remove(v); - } - } - } while ((l_iter = l_iter->next) != l_first); - - /* Remove face from node and top level. */ - f_node->bm_faces.remove(f); - BM_ELEM_CD_SET_INT(f, pbvh->cd_face_node_offset, DYNTOPO_NODE_NONE); - - /* Log removed face. */ - BM_log_face_removed(pbvh->bm_log, f); - - /* Mark node for update. */ - f_node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateNormals | PBVH_TopologyUpdated; -} - -static Array pbvh_bmesh_edge_loops(BMEdge *e) -{ - /* Fast-path for most common case where an edge has 2 faces no need to iterate twice. */ - std::array manifold_loops; - if (LIKELY(BM_edge_loop_pair(e, &manifold_loops[0], &manifold_loops[1]))) { - return Array(Span(manifold_loops)); - } - Array loops(BM_edge_face_count(e)); - BM_iter_as_array( - nullptr, BM_LOOPS_OF_EDGE, e, reinterpret_cast(loops.data()), loops.size()); - return loops; -} - -static void pbvh_bmesh_node_drop_orig(PBVHNode *node) -{ - MEM_SAFE_FREE(node->bm_orco); - MEM_SAFE_FREE(node->bm_ortri); - MEM_SAFE_FREE(node->bm_orvert); - node->bm_tot_ortri = 0; -} - -/****************************** EdgeQueue *****************************/ - -struct EdgeQueue { - HeapSimple *heap; - const float *center; - float center_proj[3]; /* For when we use projected coords. */ - float radius_squared; - float limit_len_squared; -#ifdef USE_EDGEQUEUE_EVEN_SUBDIV - float limit_len; -#endif - - bool (*edge_queue_tri_in_range)(const EdgeQueue *q, BMFace *f); - - const float *view_normal; -#ifdef USE_EDGEQUEUE_FRONTFACE - uint use_view_normal : 1; -#endif -}; - -struct EdgeQueueContext { - EdgeQueue *q; - BLI_mempool *pool; - BMesh *bm; - int cd_vert_mask_offset; - int cd_vert_node_offset; - int cd_face_node_offset; -}; - -/* Only tagged edges are in the queue. */ -#ifdef USE_EDGEQUEUE_TAG -# define EDGE_QUEUE_TEST(e) BM_elem_flag_test((CHECK_TYPE_INLINE(e, BMEdge *), e), BM_ELEM_TAG) -# define EDGE_QUEUE_ENABLE(e) \ - BM_elem_flag_enable((CHECK_TYPE_INLINE(e, BMEdge *), e), BM_ELEM_TAG) -# define EDGE_QUEUE_DISABLE(e) \ - BM_elem_flag_disable((CHECK_TYPE_INLINE(e, BMEdge *), e), BM_ELEM_TAG) -#endif - -#ifdef USE_EDGEQUEUE_TAG_VERIFY -/* simply check no edges are tagged - * (it's a requirement that edges enter and leave a clean tag state) */ -static void pbvh_bmesh_edge_tag_verify(PBVH *pbvh) -{ - for (int n = 0; n < pbvh->totnode; n++) { - PBVHNode *node = &pbvh->nodes[n]; - if (node->bm_faces) { - GSetIterator gs_iter; - GSET_ITER (gs_iter, node->bm_faces) { - BMFace *f = BLI_gsetIterator_getKey(&gs_iter); - BMEdge *e_tri[3]; - BMLoop *l_iter; - - BLI_assert(f->len == 3); - l_iter = BM_FACE_FIRST_LOOP(f); - e_tri[0] = l_iter->e; - l_iter = l_iter->next; - e_tri[1] = l_iter->e; - l_iter = l_iter->next; - e_tri[2] = l_iter->e; - - BLI_assert((EDGE_QUEUE_TEST(e_tri[0]) == false) && (EDGE_QUEUE_TEST(e_tri[1]) == false) && - (EDGE_QUEUE_TEST(e_tri[2]) == false)); - } - } - } -} -#endif - -static bool edge_queue_tri_in_sphere(const EdgeQueue *q, BMFace *f) -{ - BMVert *v_tri[3]; - float c[3]; - - /* Get closest point in triangle to sphere center. */ - BM_face_as_array_vert_tri(f, v_tri); - - closest_on_tri_to_point_v3(c, q->center, v_tri[0]->co, v_tri[1]->co, v_tri[2]->co); - - /* Check if triangle intersects the sphere. */ - return len_squared_v3v3(q->center, c) <= q->radius_squared; -} - -static bool edge_queue_tri_in_circle(const EdgeQueue *q, BMFace *f) -{ - BMVert *v_tri[3]; - float c[3]; - float tri_proj[3][3]; - - /* Get closest point in triangle to sphere center. */ - BM_face_as_array_vert_tri(f, v_tri); - - project_plane_normalized_v3_v3v3(tri_proj[0], v_tri[0]->co, q->view_normal); - project_plane_normalized_v3_v3v3(tri_proj[1], v_tri[1]->co, q->view_normal); - project_plane_normalized_v3_v3v3(tri_proj[2], v_tri[2]->co, q->view_normal); - - closest_on_tri_to_point_v3(c, q->center_proj, tri_proj[0], tri_proj[1], tri_proj[2]); - - /* Check if triangle intersects the sphere. */ - return len_squared_v3v3(q->center_proj, c) <= q->radius_squared; -} - -/** Return true if the vertex mask is less than 1.0, false otherwise. */ -static bool check_mask(EdgeQueueContext *eq_ctx, BMVert *v) -{ - return BM_ELEM_CD_GET_FLOAT(v, eq_ctx->cd_vert_mask_offset) < 1.0f; -} - -static void edge_queue_insert(EdgeQueueContext *eq_ctx, BMEdge *e, float priority) -{ - /* Don't let topology update affect fully masked vertices. This used to - * have a 50% mask cutoff, with the reasoning that you can't do a 50% - * topology update. But this gives an ugly border in the mesh. The mask - * should already make the brush move the vertices only 50%, which means - * that topology updates will also happen less frequent, that should be - * enough. */ - if (((eq_ctx->cd_vert_mask_offset == -1) || - (check_mask(eq_ctx, e->v1) || check_mask(eq_ctx, e->v2))) && - !(BM_elem_flag_test_bool(e->v1, BM_ELEM_HIDDEN) || - BM_elem_flag_test_bool(e->v2, BM_ELEM_HIDDEN))) - { - BMVert **pair = static_cast(BLI_mempool_alloc(eq_ctx->pool)); - pair[0] = e->v1; - pair[1] = e->v2; - BLI_heapsimple_insert(eq_ctx->q->heap, priority, pair); -#ifdef USE_EDGEQUEUE_TAG - BLI_assert(EDGE_QUEUE_TEST(e) == false); - EDGE_QUEUE_ENABLE(e); -#endif - } -} - -/** Return true if the edge is a boundary edge: both its vertices are on a boundary. */ -static bool is_boundary_edge(const BMEdge &edge) -{ - if (edge.head.hflag & BM_ELEM_SEAM) { - return true; - } - if ((edge.head.hflag & BM_ELEM_SMOOTH) == 0) { - return true; - } - if (!BM_edge_is_manifold(&edge)) { - return true; - } - - /* TODO(@sergey): Other boundaries? For example, edges between two different face sets. */ - - return false; -} - -/* Return true if the vertex is adjacent to a boundary edge. */ -static bool is_boundary_vert(const BMVert &vertex) -{ - BMEdge *edge = vertex.e; - BMEdge *first_edge = edge; - do { - if (is_boundary_edge(*edge)) { - return true; - } - } while ((edge = BM_DISK_EDGE_NEXT(edge, &vertex)) != first_edge); - - return false; -} - -/** Return true if at least one of the edge vertices is adjacent to a boundary. */ -static bool is_edge_adjacent_to_boundary(const BMEdge &edge) -{ - return is_boundary_vert(*edge.v1) || is_boundary_vert(*edge.v2); -} - -/* Notes on edge priority. - * - * The priority is used to control the order in which edges are handled for both splitting of long - * edges and collapsing of short edges. For long edges we start by splitting the longest edge and - * for collapsing we start with the shortest. - * - * A heap-like data structure is used to accelerate such ordering. A bit confusingly, this data - * structure gives the higher priorities to elements with lower numbers. - * - * When edges do not belong to and are not adjacent to boundaries, their length is used as the - * priority directly. Prefer to handle those edges first. Modifying those edges leads to no - * distortion to the boundary. - * - * Edges adjacent to a boundary with one vertex are handled next, and the vertex which is - * on the boundary does not change position as part of the edge collapse algorithm. - * - * And last, the boundary edges are handled. While subdivision of boundary edges does not change - * the shape of the boundary, collapsing boundary edges distorts the boundary. Hence they are - * handled last. */ - -static float long_edge_queue_priority(const BMEdge &edge) -{ - return -BM_edge_calc_length_squared(&edge); -} - -static float short_edge_queue_priority(const BMEdge &edge) -{ - float priority = BM_edge_calc_length_squared(&edge); - - if (is_boundary_edge(edge)) { - priority *= 1.5f; - } - else if (is_edge_adjacent_to_boundary(edge)) { - priority *= 1.25f; - } - - return priority; -} - -static void long_edge_queue_edge_add(EdgeQueueContext *eq_ctx, BMEdge *e) -{ -#ifdef USE_EDGEQUEUE_TAG - if (EDGE_QUEUE_TEST(e) == false) -#endif - { - const float len_sq = BM_edge_calc_length_squared(e); - if (len_sq > eq_ctx->q->limit_len_squared) { - edge_queue_insert(eq_ctx, e, long_edge_queue_priority(*e)); - } - } -} - -#ifdef USE_EDGEQUEUE_EVEN_SUBDIV -static void long_edge_queue_edge_add_recursive( - EdgeQueueContext *eq_ctx, BMLoop *l_edge, BMLoop *l_end, const float len_sq, float limit_len) -{ - BLI_assert(len_sq > square_f(limit_len)); - -# ifdef USE_EDGEQUEUE_FRONTFACE - if (eq_ctx->q->use_view_normal) { - if (dot_v3v3(l_edge->f->no, eq_ctx->q->view_normal) < 0.0f) { - return; - } - } -# endif - -# ifdef USE_EDGEQUEUE_TAG - if (EDGE_QUEUE_TEST(l_edge->e) == false) -# endif - { - edge_queue_insert(eq_ctx, l_edge->e, long_edge_queue_priority(*l_edge->e)); - } - - /* temp support previous behavior! */ - if (UNLIKELY(G.debug_value == 1234)) { - return; - } - - if (l_edge->radial_next != l_edge) { - /* How much longer we need to be to consider for subdividing - * (avoids subdividing faces which are only *slightly* skinny). */ -# define EVEN_EDGELEN_THRESHOLD 1.2f - /* How much the limit increases per recursion - * (avoids performing subdivisions too far away). */ -# define EVEN_GENERATION_SCALE 1.6f - - const float len_sq_cmp = len_sq * EVEN_EDGELEN_THRESHOLD; - - limit_len *= EVEN_GENERATION_SCALE; - const float limit_len_sq = square_f(limit_len); - - BMLoop *l_iter = l_edge; - do { - BMLoop *l_adjacent[2] = {l_iter->next, l_iter->prev}; - for (int i = 0; i < ARRAY_SIZE(l_adjacent); i++) { - float len_sq_other = BM_edge_calc_length_squared(l_adjacent[i]->e); - if (len_sq_other > max_ff(len_sq_cmp, limit_len_sq)) { - // edge_queue_insert(eq_ctx, l_adjacent[i]->e, -len_sq_other); - long_edge_queue_edge_add_recursive( - eq_ctx, l_adjacent[i]->radial_next, l_adjacent[i], len_sq_other, limit_len); - } - } - } while ((l_iter = l_iter->radial_next) != l_end); - -# undef EVEN_EDGELEN_THRESHOLD -# undef EVEN_GENERATION_SCALE - } -} -#endif /* USE_EDGEQUEUE_EVEN_SUBDIV */ - -static void short_edge_queue_edge_add(EdgeQueueContext *eq_ctx, BMEdge *e) -{ -#ifdef USE_EDGEQUEUE_TAG - if (EDGE_QUEUE_TEST(e) == false) -#endif - { - const float len_sq = BM_edge_calc_length_squared(e); - if (len_sq < eq_ctx->q->limit_len_squared) { - edge_queue_insert(eq_ctx, e, short_edge_queue_priority(*e)); - } - } -} - -static void long_edge_queue_face_add(EdgeQueueContext *eq_ctx, BMFace *f) -{ -#ifdef USE_EDGEQUEUE_FRONTFACE - if (eq_ctx->q->use_view_normal) { - if (dot_v3v3(f->no, eq_ctx->q->view_normal) < 0.0f) { - return; - } - } -#endif - - if (eq_ctx->q->edge_queue_tri_in_range(eq_ctx->q, f)) { - /* Check each edge of the face. */ - BMLoop *l_first = BM_FACE_FIRST_LOOP(f); - BMLoop *l_iter = l_first; - do { -#ifdef USE_EDGEQUEUE_EVEN_SUBDIV - const float len_sq = BM_edge_calc_length_squared(l_iter->e); - if (len_sq > eq_ctx->q->limit_len_squared) { - long_edge_queue_edge_add_recursive( - eq_ctx, l_iter->radial_next, l_iter, len_sq, eq_ctx->q->limit_len); - } -#else - long_edge_queue_edge_add(eq_ctx, l_iter->e); -#endif - } while ((l_iter = l_iter->next) != l_first); - } -} - -static void short_edge_queue_face_add(EdgeQueueContext *eq_ctx, BMFace *f) -{ -#ifdef USE_EDGEQUEUE_FRONTFACE - if (eq_ctx->q->use_view_normal) { - if (dot_v3v3(f->no, eq_ctx->q->view_normal) < 0.0f) { - return; - } - } -#endif - - if (eq_ctx->q->edge_queue_tri_in_range(eq_ctx->q, f)) { - BMLoop *l_iter; - BMLoop *l_first; - - /* Check each edge of the face. */ - l_iter = l_first = BM_FACE_FIRST_LOOP(f); - do { - short_edge_queue_edge_add(eq_ctx, l_iter->e); - } while ((l_iter = l_iter->next) != l_first); - } -} - -/** - * Create a priority queue containing vertex pairs connected by a long - * edge as defined by PBVH.bm_max_edge_len. - * - * Only nodes marked for topology update are checked, and in those - * nodes only edges used by a face intersecting the (center, radius) - * sphere are checked. - * - * The highest priority (lowest number) is given to the longest edge. - */ -static void long_edge_queue_create(EdgeQueueContext *eq_ctx, - PBVH *pbvh, - const float center[3], - const float view_normal[3], - float radius, - const bool use_frontface, - const bool use_projected) -{ - eq_ctx->q->heap = BLI_heapsimple_new(); - eq_ctx->q->center = center; - eq_ctx->q->radius_squared = radius * radius; - eq_ctx->q->limit_len_squared = pbvh->bm_max_edge_len * pbvh->bm_max_edge_len; -#ifdef USE_EDGEQUEUE_EVEN_SUBDIV - eq_ctx->q->limit_len = pbvh->bm_max_edge_len; -#endif - - eq_ctx->q->view_normal = view_normal; - -#ifdef USE_EDGEQUEUE_FRONTFACE - eq_ctx->q->use_view_normal = use_frontface; -#else - UNUSED_VARS(use_frontface); -#endif - - if (use_projected) { - eq_ctx->q->edge_queue_tri_in_range = edge_queue_tri_in_circle; - project_plane_normalized_v3_v3v3(eq_ctx->q->center_proj, center, view_normal); - } - else { - eq_ctx->q->edge_queue_tri_in_range = edge_queue_tri_in_sphere; - } - -#ifdef USE_EDGEQUEUE_TAG_VERIFY - pbvh_bmesh_edge_tag_verify(pbvh); -#endif - - for (PBVHNode &node : pbvh->nodes) { - /* Check leaf nodes marked for topology update. */ - if ((node.flag & PBVH_Leaf) && (node.flag & PBVH_UpdateTopology) && - !(node.flag & PBVH_FullyHidden)) - { - for (BMFace *f : node.bm_faces) { - long_edge_queue_face_add(eq_ctx, f); - } - } - } -} - -/** - * Create a priority queue containing vertex pairs connected by a - * short edge as defined by PBVH.bm_min_edge_len. - * - * Only nodes marked for topology update are checked, and in those - * nodes only edges used by a face intersecting the (center, radius) - * sphere are checked. - * - * The highest priority (lowest number) is given to the shortest edge. - */ -static void short_edge_queue_create(EdgeQueueContext *eq_ctx, - PBVH *pbvh, - const float center[3], - const float view_normal[3], - float radius, - const bool use_frontface, - const bool use_projected) -{ - eq_ctx->q->heap = BLI_heapsimple_new(); - eq_ctx->q->center = center; - eq_ctx->q->radius_squared = radius * radius; - eq_ctx->q->limit_len_squared = pbvh->bm_min_edge_len * pbvh->bm_min_edge_len; -#ifdef USE_EDGEQUEUE_EVEN_SUBDIV - eq_ctx->q->limit_len = pbvh->bm_min_edge_len; -#endif - - eq_ctx->q->view_normal = view_normal; - -#ifdef USE_EDGEQUEUE_FRONTFACE - eq_ctx->q->use_view_normal = use_frontface; -#else - UNUSED_VARS(use_frontface); -#endif - - if (use_projected) { - eq_ctx->q->edge_queue_tri_in_range = edge_queue_tri_in_circle; - project_plane_normalized_v3_v3v3(eq_ctx->q->center_proj, center, view_normal); - } - else { - eq_ctx->q->edge_queue_tri_in_range = edge_queue_tri_in_sphere; - } - - for (PBVHNode &node : pbvh->nodes) { - /* Check leaf nodes marked for topology update */ - if ((node.flag & PBVH_Leaf) && (node.flag & PBVH_UpdateTopology) && - !(node.flag & PBVH_FullyHidden)) - { - for (BMFace *f : node.bm_faces) { - short_edge_queue_face_add(eq_ctx, f); - } - } - } -} - -/*************************** Topology update **************************/ - -/** - * Copy custom data from src to dst edge. - * - * \note The BM_ELEM_TAG is used to tell whether an edge is in the queue for collapse/split, - * so we do not copy this flag as we do not want the new edge to appear in the queue. - */ -static void copy_edge_data(BMesh &bm, BMEdge &dst, /*const*/ BMEdge &src) -{ - dst.head.hflag = src.head.hflag & ~BM_ELEM_TAG; - CustomData_bmesh_copy_block(bm.edata, src.head.data, &dst.head.data); -} - -/* Merge edge custom data from src to dst. */ -static void merge_edge_data(BMesh &bm, BMEdge &dst, const BMEdge &src) -{ - dst.head.hflag |= (src.head.hflag & ~(BM_ELEM_TAG | BM_ELEM_SMOOTH)); - - /* If either of the src or dst is sharp the result is sharp. */ - if ((src.head.hflag & BM_ELEM_SMOOTH) == 0) { - dst.head.hflag &= ~BM_ELEM_SMOOTH; - } - - BM_data_interp_from_edges(&bm, &src, &dst, &dst, 0.5f); -} - -static void pbvh_bmesh_split_edge(EdgeQueueContext *eq_ctx, PBVH *pbvh, BMEdge *e) -{ - BMesh *bm = pbvh->header.bm; - - float co_mid[3], no_mid[3]; - - /* Get all faces adjacent to the edge. */ - Array edge_loops = pbvh_bmesh_edge_loops(e); - - /* Create a new vertex in current node at the edge's midpoint. */ - mid_v3_v3v3(co_mid, e->v1->co, e->v2->co); - mid_v3_v3v3(no_mid, e->v1->no, e->v2->no); - normalize_v3(no_mid); - - int node_index = BM_ELEM_CD_GET_INT(e->v1, eq_ctx->cd_vert_node_offset); - BMVert *v_new = pbvh_bmesh_vert_create( - pbvh, e->v1, e->v2, node_index, co_mid, no_mid, eq_ctx->cd_vert_mask_offset); - - /* For each face, add two new triangles and delete the original. */ - for (const int i : edge_loops.index_range()) { - BMLoop *l_adj = edge_loops[i]; - BMFace *f_adj = l_adj->f; - - BLI_assert(f_adj->len == 3); - int ni = BM_ELEM_CD_GET_INT(f_adj, eq_ctx->cd_face_node_offset); - - /* Find the vertex not in the edge. */ - BMVert *v_opp = l_adj->prev->v; - - /* Get e->v1 and e->v2 in the order they appear in the existing face so that the new faces' - * winding orders match. */ - BMVert *v1 = l_adj->v; - BMVert *v2 = l_adj->next->v; - - if (ni != node_index && i == 0) { - pbvh_bmesh_vert_ownership_transfer(pbvh, &pbvh->nodes[ni], v_new); - } - - /* The 2 new faces created and assigned to `f_new` have their - * verts & edges shuffled around. - * - * - faces wind anticlockwise in this example. - * - original edge is `(v1, v2)` - * - original face is `(v1, v2, v3)` - * - *
-     *         + v_opp
-     *        /|\
-     *       / | \
-     *      /  |  \
-     *   e4/   |   \ e3
-     *    /    |e5  \
-     *   /     |     \
-     *  /  e1  |  e2  \
-     * +-------+-------+
-     * v1      v_new     v2
-     *  (first) (second)
-     * 
- * - * - f_new (first): `v_tri=(v1, v_new, v_opp), e_tri=(e1, e5, e4)` - * - f_new (second): `v_tri=(v_new, v2, v_opp), e_tri=(e2, e3, e5)` - */ - - /* Create first face (v1, v_new, v_opp). */ - const std::array first_tri({v1, v_new, v_opp}); - const std::array first_edges = bm_edges_from_tri(bm, first_tri); - copy_edge_data(*bm, *first_edges[0], *e); - - BMFace *f_new_first = pbvh_bmesh_face_create(pbvh, ni, first_tri, first_edges, f_adj); - long_edge_queue_face_add(eq_ctx, f_new_first); - - /* Create second face (v_new, v2, v_opp). */ - const std::array second_tri({v_new, v2, v_opp}); - const std::array second_edges{ - BM_edge_create(bm, second_tri[0], second_tri[1], nullptr, BM_CREATE_NO_DOUBLE), - BM_edge_create(bm, second_tri[1], second_tri[2], nullptr, BM_CREATE_NO_DOUBLE), - first_edges[1], - }; - copy_edge_data(*bm, *second_edges[0], *e); - - BMFace *f_new_second = pbvh_bmesh_face_create(pbvh, ni, second_tri, second_edges, f_adj); - long_edge_queue_face_add(eq_ctx, f_new_second); - - /* Delete original */ - pbvh_bmesh_face_remove(pbvh, f_adj); - BM_face_kill(bm, f_adj); - - /* Ensure new vertex is in the node */ - if (!pbvh->nodes[ni].bm_unique_verts.contains(v_new)) { - pbvh->nodes[ni].bm_other_verts.add(v_new); - } - - if (BM_vert_edge_count_is_over(v_opp, 8)) { - BMIter bm_iter; - BMEdge *e2; - BM_ITER_ELEM (e2, &bm_iter, v_opp, BM_EDGES_OF_VERT) { - long_edge_queue_edge_add(eq_ctx, e2); - } - } - } - - BM_edge_kill(bm, e); -} - -static bool pbvh_bmesh_subdivide_long_edges(EdgeQueueContext *eq_ctx, PBVH *pbvh) -{ - const double start_time = BLI_time_now_seconds(); - - bool any_subdivided = false; - - while (!BLI_heapsimple_is_empty(eq_ctx->q->heap)) { - BMVert **pair = static_cast(BLI_heapsimple_pop_min(eq_ctx->q->heap)); - BMVert *v1 = pair[0]; - BMVert *v2 = pair[1]; - - BLI_mempool_free(eq_ctx->pool, pair); - pair = nullptr; - - /* Check that the edge still exists */ - BMEdge *e; - if (!(e = BM_edge_exists(v1, v2))) { - continue; - } -#ifdef USE_EDGEQUEUE_TAG - EDGE_QUEUE_DISABLE(e); -#endif - - BLI_assert(len_squared_v3v3(v1->co, v2->co) > eq_ctx->q->limit_len_squared); - - /* Check that the edge's vertices are still in the PBVH. It's - * possible that an edge collapse has deleted adjacent faces - * and the node has been split, thus leaving wire edges and - * associated vertices. */ - if ((BM_ELEM_CD_GET_INT(e->v1, eq_ctx->cd_vert_node_offset) == DYNTOPO_NODE_NONE) || - (BM_ELEM_CD_GET_INT(e->v2, eq_ctx->cd_vert_node_offset) == DYNTOPO_NODE_NONE)) - { + if (!(node->flag & PBVH_Leaf) || !(node->flag & PBVH_RebuildNodeVerts)) { continue; } - any_subdivided = true; - - pbvh_bmesh_split_edge(eq_ctx, pbvh, e); + pbvh_bmesh_regen_node_verts(pbvh, node, report); } - -#ifdef USE_EDGEQUEUE_TAG_VERIFY - pbvh_bmesh_edge_tag_verify(pbvh); -#endif - - CLOG_INFO( - &LOG, 2, "Long edge subdivision took %f seconds.", BLI_time_now_seconds() - start_time); - - return any_subdivided; } -/** Check whether the \a vert is adjacent to any face which are adjacent to the #edge. */ -static bool vert_in_face_adjacent_to_edge(BMVert &vert, BMEdge &edge) +/************************* Called from pbvh.c *************************/ + +static bool pbvh_poly_hidden(PBVH * /*pbvh*/, BMFace *f) { - BMIter bm_iter; - BMFace *face; - BM_ITER_ELEM (face, &bm_iter, &edge, BM_FACES_OF_EDGE) { - if (BM_vert_in_face(&vert, face)) { - return true; - } - } - return false; + return BM_elem_flag_test(f, BM_ELEM_HIDDEN); } -/** - * Merge attributes of a flap face into an edge which will remain after the edge collapse in - * #pbvh_bmesh_collapse_edge. - * - * This function is to be called before faces adjacent to \a e are deleted. - * This function only handles edge attributes and does not handle face deletion. - * - * \param del_face: Face which is adjacent to \a v_del and will form a flap when merging \a v_del - * to \a v_conn. - * \param flap_face: Face which is adjacent to \a v_conn and will form a flap when merging \a v_del - * to \a v_conn. - * \param e: An edge which is being collapsed. It connects \a v_del and \a v_conn. - * \param v_del: A vertex which will be removed after the edge collapse. - * \param l_del: A loop of del_face which is adjacent to v_del. - * \param v_conn: A vertex which into which geometry is reconnected to after the edge collapse. - */ -static void merge_flap_edge_data(BMesh &bm, - BMFace *del_face, - BMFace *flap_face, - BMEdge *e, - BMVert *v_del, - BMLoop *l_del, - BMVert *v_conn) +bool BKE_pbvh_bmesh_check_origdata(SculptSession *ss, BMVert *v, int /*stroke_id*/) { - /* - * v_del - * + - * del_face . / | - * . / | - * . / | - * v1 +---------------------+ v2 | - * . \ | - * . \ | - * . \ | - * flap_face + - * v_conn - * - * - */ - - UNUSED_VARS_NDEBUG(del_face, flap_face); - - /* Faces around `e` (which connects `v_del` to `v_conn`) are to the handled separately from this - * function. Help troubleshooting cases where these faces are mistakenly considered flaps. */ - BLI_assert(!BM_edge_in_face(e, del_face)); - BLI_assert(!BM_edge_in_face(e, flap_face)); - - /* The `l_del->next->v` and `l_del->prev->v` are v1 and v2, but in an unknown order. */ - BMEdge *edge_v1_v2 = BM_edge_exists(l_del->next->v, l_del->prev->v); - if (!edge_v1_v2) { - CLOG_WARN(&LOG, "Unable to find edge shared between deleting and flap faces"); - return; - } - - BLI_assert(BM_edge_in_face(edge_v1_v2, del_face)); - BLI_assert(BM_edge_in_face(edge_v1_v2, flap_face)); - - /* Disambiguate v1 from v2: the v2 is adjacent to a face around #e. */ - BMVert *v2 = vert_in_face_adjacent_to_edge(*edge_v1_v2->v1, *e) ? edge_v1_v2->v1 : - edge_v1_v2->v2; - BMVert *v1 = BM_edge_other_vert(edge_v1_v2, v2); - - /* Merge attributes into an edge (v1, v_conn). */ - BMEdge *dst_edge = BM_edge_exists(v1, v_conn); - - const std::array source_edges{ - /* Edges of the `flap_face`. - * The face will be deleted, effectively being "collapsed" into an edge. */ - edge_v1_v2, - BM_edge_exists(v2, v_conn), - - /* Edges of the `del_face`. - * These edges are implicitly merged with the ones from the `flap_face` upon collapsing edge - * `e`. */ - BM_edge_exists(v1, v_del), - BM_edge_exists(v2, v_del), - }; - - for (const BMEdge *src_edge : source_edges) { - if (!src_edge) { - CLOG_WARN(&LOG, "Unable to find source edge for flap attributes merge"); - continue; - } - - merge_edge_data(bm, *dst_edge, *src_edge); - } + PBVHVertRef vertex = {(intptr_t)v}; + return blender::bke::paint::get_original_vertex(ss, vertex, nullptr, nullptr, nullptr, nullptr); } -/** - * Find vertex which can be an outer for the flap face: the vertex will become loose when the face - * and its edges are removed. - * If there are multiple of such vertices, return null. - */ -static BMVert *find_outer_flap_vert(BMFace &face) -{ - BMVert *flap_vert = nullptr; - - BMIter bm_iter; - BMVert *vert; - BM_ITER_ELEM (vert, &bm_iter, &face, BM_VERTS_OF_FACE) { - if (BM_vert_face_count_at_most(vert, 2) == 1) { - if (flap_vert) { - /* There are multiple vertices which become loose on removing the face and its edges. */ - return nullptr; - } - flap_vert = vert; - } - } - - return flap_vert; -} - -/** - * If the `del_face` is a flap, merge edge data from edges adjacent to "corner" vertex into the - * other edge. The "corner" as it is an "outer", or a vertex which will become loose when the - * `del_face` and its edges are removed. - * - * If the face is not a flap then this function does nothing. - */ -static void try_merge_flap_edge_data_before_dissolve(BMesh &bm, BMFace &face) -{ - /* - * v1 v2 - * ... ------ + ----------------- + ------ ... - * \ / - * \ / - * \ / - * \ / - * \ / - * + v_flap - */ - - BMVert *v_flap = find_outer_flap_vert(face); - if (!v_flap) { - return; - } - - BMLoop *l_flap = BM_vert_find_first_loop(v_flap); - BLI_assert(l_flap->v == v_flap); - - /* Edges which are adjacent ot the v_flap. */ - BMEdge *edge_1 = l_flap->prev->e; - BMEdge *edge_2 = l_flap->e; - - BLI_assert(BM_edge_face_count(edge_1) == 1); - BLI_assert(BM_edge_face_count(edge_2) == 1); - - BMEdge *edge_v1_v2 = l_flap->next->e; - - merge_edge_data(bm, *edge_v1_v2, *edge_1); - merge_edge_data(bm, *edge_v1_v2, *edge_2); -} - -/** - * Merge attributes of edges from \a v_del to \a f - * - * This function is to be called before faces adjacent to \a e are deleted. - * This function only handles edge attributes. and does not handle face deletion. - * - * \param del_face: Face which is adjacent to \a v_del and will be deleted as part of merging - * \a v_del to \a v_conn. - * \param new_face: A new face which is created from \a del_face by replacing \a v_del with - * \a v_conn. - * \param v_del: A vertex which will be removed after the edge collapse. - * \param l_del: A loop of del_face which is adjacent to v_del. - * \param v_conn: A vertex which into which geometry is reconnected to after the edge collapse. - */ -static void merge_face_edge_data(BMesh &bm, - BMFace * /*del_face*/, - BMFace *new_face, - BMVert *v_del, - BMLoop *l_del, - BMVert *v_conn) -{ - /* When collapsing an edge (v_conn, v_del) a face (v_conn, v2, v_del) is to be deleted and the - * v_del reference in the face (v_del, v2, v1) is to be replaced with v_conn. Doing vertex - * reference replacement in BMesh is not trivial. so for the simplicity the - * #pbvh_bmesh_collapse_edge deletes both original faces and creates new one (c_conn, v2, v1). - * - * When doing such re-creating attributes from old edges are to be merged into the new ones: - * - Attributes of (v_del, v1) needs to be merged into (v_conn, v1), - * - Attributes of (v_del, v2) needs to be merged into (v_conn, v2), - * - *
-   *
-   *            v2
-   *             +
-   *            /|\
-   *           / | \
-   *          /  |  \
-   *         /   |   \
-   *        /    |    \
-   *       /     |     \
-   *      /      |      \
-   *     +-------+-------+
-   *  v_conn   v_del      v1
-   *
-   * 
- */ - - /* The l_del->next->v and l_del->prev->v are v1 and v2, but in an unknown order. */ - BMEdge *edge_v1_v2 = BM_edge_exists(l_del->next->v, l_del->prev->v); - if (!edge_v1_v2) { - CLOG_WARN(&LOG, "Unable to find edge shared between old and new faces"); - return; - } - - BMIter bm_iter; - BMEdge *dst_edge; - BM_ITER_ELEM (dst_edge, &bm_iter, new_face, BM_EDGES_OF_FACE) { - if (dst_edge == edge_v1_v2) { - continue; - } - - BLI_assert(BM_vert_in_edge(dst_edge, v_conn)); - - /* Depending on an edge v_other will be v1 or v2. */ - BMVert *v_other = BM_edge_other_vert(dst_edge, v_conn); - - BMEdge *src_edge = BM_edge_exists(v_del, v_other); - BLI_assert(src_edge); - - if (src_edge) { - merge_edge_data(bm, *dst_edge, *src_edge); - } - else { - CLOG_WARN(&LOG, "Unable to find edge to merge attributes from"); - } - } -} - -static void pbvh_bmesh_collapse_edge( - PBVH *pbvh, BMEdge *e, BMVert *v1, BMVert *v2, GHash *deleted_verts, EdgeQueueContext *eq_ctx) -{ - BMesh &bm = *pbvh->header.bm; - - const bool v1_on_boundary = is_boundary_vert(*v1); - const bool v2_on_boundary = is_boundary_vert(*v2); - - BMVert *v_del; - BMVert *v_conn; - if (v1_on_boundary || v2_on_boundary) { - /* Boundary edges can be collapsed with minimal distortion. For those it does not - * matter too much which vertex to keep and which one to remove. - * - * For edges which are adjacent to boundaries, keep the vertex which is on boundary and - * dissolve the other one. */ - if (v1_on_boundary) { - v_del = v2; - v_conn = v1; - } - else { - v_del = v1; - v_conn = v2; - } - } - else if (BM_ELEM_CD_GET_FLOAT(v1, eq_ctx->cd_vert_mask_offset) < - BM_ELEM_CD_GET_FLOAT(v2, eq_ctx->cd_vert_mask_offset)) - { - /* Prefer deleting the vertex that is less masked. */ - v_del = v1; - v_conn = v2; - } - else { - v_del = v2; - v_conn = v1; - } - - /* Remove the merge vertex from the PBVH. */ - pbvh_bmesh_vert_remove(pbvh, v_del); - - /* For all remaining faces of v_del, create a new face that is the - * same except it uses v_conn instead of v_del */ - /* NOTE: this could be done with BM_vert_splice(), but that requires handling other issues like - * duplicate edges, so it wouldn't really buy anything. */ - Vector deleted_faces; - - BMLoop *l; - BM_LOOPS_OF_VERT_ITER_BEGIN (l, v_del) { - BMFace *f_del = l->f; - - /* Ignore faces around `e`: they will be deleted explicitly later on. - * Without ignoring these faces the #bm_face_exists_tri_from_loop_vert() triggers an assert. */ - if (BM_edge_in_face(e, f_del)) { - continue; - } - - /* Schedule the faces adjacent to the v_del for deletion first. - * This way we know that it will be #existing_face which is deleted last when deleting faces - * which forms a flap. */ - deleted_faces.append(f_del); - - /* Check if a face using these vertices already exists. If so, skip adding this face and mark - * the existing one for deletion as well. Prevents extraneous "flaps" from being created. - * Check is similar to #BM_face_exists. */ - if (BMFace *existing_face = bm_face_exists_tri_from_loop_vert(l->next, v_conn)) { - merge_flap_edge_data(bm, f_del, existing_face, e, v_del, l, v_conn); - - deleted_faces.append(existing_face); - } - } - BM_LOOPS_OF_VERT_ITER_END; - - /* Remove all faces adjacent to the edge. */ - BMLoop *l_adj; - while ((l_adj = e->l)) { - BMFace *f_adj = l_adj->f; - - pbvh_bmesh_face_remove(pbvh, f_adj); - BM_face_kill(&bm, f_adj); - } - - /* Kill the edge. */ - BLI_assert(BM_edge_is_wire(e)); - BM_edge_kill(&bm, e); - - BM_LOOPS_OF_VERT_ITER_BEGIN (l, v_del) { - /* Get vertices, replace use of v_del with v_conn */ - BMFace *f = l->f; - - if (bm_face_exists_tri_from_loop_vert(l->next, v_conn)) { - /* This case is handled above. */ - } - else { - const std::array v_tri{v_conn, l->next->v, l->prev->v}; - - BLI_assert(!BM_face_exists(v_tri.data(), 3)); - PBVHNode *n = pbvh_bmesh_node_from_face(pbvh, f); - int ni = n - pbvh->nodes.data(); - const std::array e_tri = bm_edges_from_tri(&bm, v_tri); - BMFace *new_face = pbvh_bmesh_face_create(pbvh, ni, v_tri, e_tri, f); - - merge_face_edge_data(bm, f, new_face, v_del, l, v_conn); - - /* Ensure that v_conn is in the new face's node */ - if (!n->bm_unique_verts.contains(v_conn)) { - n->bm_other_verts.add(v_conn); - } - } - } - BM_LOOPS_OF_VERT_ITER_END; - - /* Delete the tagged faces. */ - for (BMFace *f_del : deleted_faces) { - /* Get vertices and edges of face. */ - BLI_assert(f_del->len == 3); - BMLoop *l_iter = BM_FACE_FIRST_LOOP(f_del); - const std::array v_tri{l_iter->v, l_iter->next->v, l_iter->next->next->v}; - const std::array e_tri{l_iter->e, l_iter->next->e, l_iter->next->next->e}; - - /* if its sa flap face merge its "outer" edge data into "base", so that boundary is propagated - * from edges which are about to be deleted to the base of the triangle and will stay attached - * to the mesh. */ - try_merge_flap_edge_data_before_dissolve(bm, *f_del); - - /* Remove the face */ - pbvh_bmesh_face_remove(pbvh, f_del); - BM_face_kill(&bm, f_del); - - /* Check if any of the face's edges are now unused by any - * face, if so delete them */ - for (const int j : IndexRange(3)) { - if (BM_edge_is_wire(e_tri[j])) { - BM_edge_kill(&bm, e_tri[j]); - } - } - - /* Check if any of the face's vertices are now unused, if so - * remove them from the PBVH */ - for (const int j : IndexRange(3)) { - if ((v_tri[j] != v_del) && (v_tri[j]->e == nullptr)) { - pbvh_bmesh_vert_remove(pbvh, v_tri[j]); - - BM_log_vert_removed(pbvh->bm_log, v_tri[j], eq_ctx->cd_vert_mask_offset); - - if (v_tri[j] == v_conn) { - v_conn = nullptr; - } - BLI_ghash_insert(deleted_verts, v_tri[j], nullptr); - BM_vert_kill(&bm, v_tri[j]); - } - } - } - - /* If the v_conn was not removed above move it to the midpoint of v_conn and v_del. Doing so - * helps avoiding long stretched and degenerated triangles. - * - * However, if the vertex is on a boundary, do not move it to preserve the shape of the - * boundary. */ - if (v_conn != nullptr && !is_boundary_vert(*v_conn)) { - BM_log_vert_before_modified(pbvh->bm_log, v_conn, eq_ctx->cd_vert_mask_offset); - mid_v3_v3v3(v_conn->co, v_conn->co, v_del->co); - add_v3_v3(v_conn->no, v_del->no); - normalize_v3(v_conn->no); - } - - if (v_conn != nullptr) { - /* Update bounding boxes attached to the connected vertex. - * Note that we can often get-away without this but causes #48779. */ - BM_LOOPS_OF_VERT_ITER_BEGIN (l, v_conn) { - PBVHNode *f_node = pbvh_bmesh_node_from_face(pbvh, l->f); - f_node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateNormals | PBVH_UpdateBB; - } - BM_LOOPS_OF_VERT_ITER_END; - } - - /* Delete v_del */ - BLI_assert(!BM_vert_face_check(v_del)); - BM_log_vert_removed(pbvh->bm_log, v_del, eq_ctx->cd_vert_mask_offset); - /* v_conn == nullptr is OK */ - BLI_ghash_insert(deleted_verts, v_del, v_conn); - BM_vert_kill(&bm, v_del); -} - -static bool pbvh_bmesh_collapse_short_edges(EdgeQueueContext *eq_ctx, PBVH *pbvh) -{ - const double start_time = BLI_time_now_seconds(); - - const float min_len_squared = pbvh->bm_min_edge_len * pbvh->bm_min_edge_len; - bool any_collapsed = false; - /* Deleted verts point to vertices they were merged into, or nullptr when removed. */ - GHash *deleted_verts = BLI_ghash_ptr_new("deleted_verts"); - - while (!BLI_heapsimple_is_empty(eq_ctx->q->heap)) { - BMVert **pair = static_cast(BLI_heapsimple_pop_min(eq_ctx->q->heap)); - BMVert *v1 = pair[0]; - BMVert *v2 = pair[1]; - BLI_mempool_free(eq_ctx->pool, pair); - pair = nullptr; - - /* Check the verts still exists. */ - if (!(v1 = bm_vert_hash_lookup_chain(deleted_verts, v1)) || - !(v2 = bm_vert_hash_lookup_chain(deleted_verts, v2)) || (v1 == v2)) - { - continue; - } - - /* Check that the edge still exists. */ - BMEdge *e; - if (!(e = BM_edge_exists(v1, v2))) { - continue; - } -#ifdef USE_EDGEQUEUE_TAG - EDGE_QUEUE_DISABLE(e); -#endif - - if (len_squared_v3v3(v1->co, v2->co) >= min_len_squared) { - continue; - } - - /* Check that the edge's vertices are still in the PBVH. It's possible that an edge collapse - * has deleted adjacent faces and the node has been split, thus leaving wire edges and - * associated vertices. */ - if ((BM_ELEM_CD_GET_INT(e->v1, eq_ctx->cd_vert_node_offset) == DYNTOPO_NODE_NONE) || - (BM_ELEM_CD_GET_INT(e->v2, eq_ctx->cd_vert_node_offset) == DYNTOPO_NODE_NONE)) - { - continue; - } - - any_collapsed = true; - - pbvh_bmesh_collapse_edge(pbvh, e, v1, v2, deleted_verts, eq_ctx); - } - - BLI_ghash_free(deleted_verts, nullptr, nullptr); - - CLOG_INFO(&LOG, 2, "Short edge collapse took %f seconds.", BLI_time_now_seconds() - start_time); - - return any_collapsed; -} - -/************************* Called from pbvh.cc *************************/ - -bool bmesh_node_raycast(PBVHNode *node, - const float ray_start[3], - const float ray_normal[3], - IsectRayPrecalc *isect_precalc, - float *depth, - bool use_original, - PBVHVertRef *r_active_vertex, - float *r_face_normal) +bool pbvh_bmesh_node_raycast(SculptSession *ss, + PBVH *pbvh, + PBVHNode *node, + const float ray_start[3], + const float ray_normal[3], + struct IsectRayPrecalc *isect_precalc, + int *hit_count, + float *depth, + bool use_original, + PBVHVertRef *r_active_vertex, + PBVHFaceRef *r_active_face, + float *r_face_normal, + int stroke_id) { bool hit = false; float nearest_vertex_co[3] = {0.0f}; - use_original = use_original && node->bm_tot_ortri; + BKE_pbvh_bmesh_check_tris(pbvh, node); - if (use_original && node->bm_tot_ortri) { - for (int i = 0; i < node->bm_tot_ortri; i++) { - float *cos[3]; + for (PBVHTri &tri : node->tribuf->tris) { + BMVert *verts[3] = { + (BMVert *)node->tribuf->verts[tri.v[0]].i, + (BMVert *)node->tribuf->verts[tri.v[1]].i, + (BMVert *)node->tribuf->verts[tri.v[2]].i, + }; - cos[0] = node->bm_orco[node->bm_ortri[i][0]]; - cos[1] = node->bm_orco[node->bm_ortri[i][1]]; - cos[2] = node->bm_orco[node->bm_ortri[i][2]]; + float *cos[3]; + float *nos[3]; - if (ray_face_intersection_tri(ray_start, isect_precalc, cos[0], cos[1], cos[2], depth)) { - hit = true; + if (use_original) { + BKE_pbvh_bmesh_check_origdata(ss, verts[0], stroke_id); + BKE_pbvh_bmesh_check_origdata(ss, verts[1], stroke_id); + BKE_pbvh_bmesh_check_origdata(ss, verts[2], stroke_id); - if (r_face_normal) { - normal_tri_v3(r_face_normal, cos[0], cos[1], cos[2]); - } + cos[0] = BM_ELEM_CD_PTR(verts[0], pbvh->cd_origco); + cos[1] = BM_ELEM_CD_PTR(verts[1], pbvh->cd_origco); + cos[2] = BM_ELEM_CD_PTR(verts[2], pbvh->cd_origco); - if (r_active_vertex) { - float location[3] = {0.0f}; - madd_v3_v3v3fl(location, ray_start, ray_normal, *depth); - for (const int j : IndexRange(3)) { - if (j == 0 || - len_squared_v3v3(location, cos[j]) < len_squared_v3v3(location, nearest_vertex_co)) - { - copy_v3_v3(nearest_vertex_co, cos[j]); - r_active_vertex->i = intptr_t(node->bm_orvert[node->bm_ortri[i][j]]); - } + nos[0] = BM_ELEM_CD_PTR(verts[0], pbvh->cd_origno); + nos[1] = BM_ELEM_CD_PTR(verts[1], pbvh->cd_origno); + nos[2] = BM_ELEM_CD_PTR(verts[2], pbvh->cd_origno); + } + else { + for (int j = 0; j < 3; j++) { + cos[j] = verts[j]->co; + nos[j] = verts[j]->no; + } + } + + if (blender::bke::pbvh::ray_face_intersection_depth_tri( + ray_start, isect_precalc, cos[0], cos[1], cos[2], depth, hit_count)) + { + hit = true; + + if (r_face_normal) { + normal_tri_v3(r_face_normal, cos[0], cos[1], cos[2]); + } + + if (r_active_vertex) { + float location[3] = {0.0f}; + madd_v3_v3v3fl(location, ray_start, ray_normal, *depth); + for (int j = 0; j < 3; j++) { + if (j == 0 || + len_squared_v3v3(location, cos[j]) < len_squared_v3v3(location, nearest_vertex_co)) + { + copy_v3_v3(nearest_vertex_co, cos[j]); + r_active_vertex->i = (intptr_t)verts[j]; } } } - } - } - else { - for (BMFace *f : node->bm_faces) { - BLI_assert(f->len == 3); - if (!BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { - BMVert *v_tri[3]; - - BM_face_as_array_vert_tri(f, v_tri); - if (ray_face_intersection_tri( - ray_start, isect_precalc, v_tri[0]->co, v_tri[1]->co, v_tri[2]->co, depth)) - { - hit = true; - - if (r_face_normal) { - normal_tri_v3(r_face_normal, v_tri[0]->co, v_tri[1]->co, v_tri[2]->co); - } - - if (r_active_vertex) { - float location[3] = {0.0f}; - madd_v3_v3v3fl(location, ray_start, ray_normal, *depth); - for (const int j : IndexRange(3)) { - if (j == 0 || len_squared_v3v3(location, v_tri[j]->co) < - len_squared_v3v3(location, nearest_vertex_co)) - { - copy_v3_v3(nearest_vertex_co, v_tri[j]->co); - r_active_vertex->i = intptr_t(v_tri[j]); - } - } - } - } + if (r_active_face) { + *r_active_face = tri.f; } } } @@ -1839,109 +1478,228 @@ bool bmesh_node_raycast(PBVHNode *node, return hit; } -bool bmesh_node_raycast_detail(PBVHNode *node, - const float ray_start[3], - IsectRayPrecalc *isect_precalc, - float *depth, - float *r_edge_length) +bool BKE_pbvh_bmesh_node_raycast_detail(PBVH *pbvh, + PBVHNode *node, + const float ray_start[3], + struct IsectRayPrecalc *isect_precalc, + float *depth, + float *r_edge_length) { if (node->flag & PBVH_FullyHidden) { return false; } - bool hit = false; - BMFace *f_hit = nullptr; + BKE_pbvh_bmesh_check_tris(pbvh, node); + for (PBVHTri &tri : node->tribuf->tris) { + BMVert *v1 = (BMVert *)node->tribuf->verts[tri.v[0]].i; + BMVert *v2 = (BMVert *)node->tribuf->verts[tri.v[1]].i; + BMVert *v3 = (BMVert *)node->tribuf->verts[tri.v[2]].i; + BMFace *f = (BMFace *)tri.f.i; - for (BMFace *f : node->bm_faces) { - BLI_assert(f->len == 3); - if (!BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { - BMVert *v_tri[3]; - bool hit_local; - BM_face_as_array_vert_tri(f, v_tri); - hit_local = ray_face_intersection_tri( - ray_start, isect_precalc, v_tri[0]->co, v_tri[1]->co, v_tri[2]->co, depth); + if (pbvh_poly_hidden(pbvh, f)) { + continue; + } - if (hit_local) { - f_hit = f; - hit = true; - } + bool hit_local = bke::pbvh::ray_face_intersection_tri( + ray_start, isect_precalc, v1->co, v2->co, v3->co, depth); + + if (hit_local) { + float len1 = len_squared_v3v3(v1->co, v2->co); + float len2 = len_squared_v3v3(v2->co, v3->co); + float len3 = len_squared_v3v3(v3->co, v1->co); + + /* detail returned will be set to the maximum allowed size, so take max here */ + *r_edge_length = sqrtf(max_fff(len1, len2, len3)); + + return true; } } - if (hit) { - BMVert *v_tri[3]; - BM_face_as_array_vert_tri(f_hit, v_tri); - float len1 = len_squared_v3v3(v_tri[0]->co, v_tri[1]->co); - float len2 = len_squared_v3v3(v_tri[1]->co, v_tri[2]->co); - float len3 = len_squared_v3v3(v_tri[2]->co, v_tri[0]->co); + return false; +} - /* Detail returned will be set to the maximum allowed size, so take max here. */ - *r_edge_length = sqrtf(max_fff(len1, len2, len3)); +bool pbvh_bmesh_node_nearest_to_ray(SculptSession *ss, + PBVH *pbvh, + PBVHNode *node, + const float ray_start[3], + const float ray_normal[3], + float *depth, + float *dist_sq, + bool use_original, + int stroke_id) +{ + bool hit = false; + + BKE_pbvh_bmesh_check_tris(pbvh, node); + PBVHTriBuf *tribuf = node->tribuf; + + for (PBVHTri &tri : tribuf->tris) { + BMFace *f = (BMFace *)tri.f.i; + + if (pbvh_poly_hidden(pbvh, f)) { + continue; + } + + BMVert *v1 = (BMVert *)tribuf->verts[tri.v[0]].i; + BMVert *v2 = (BMVert *)tribuf->verts[tri.v[1]].i; + BMVert *v3 = (BMVert *)tribuf->verts[tri.v[2]].i; + + float *co1, *co2, *co3; + + if (use_original) { + BKE_pbvh_bmesh_check_origdata(ss, v1, stroke_id); + BKE_pbvh_bmesh_check_origdata(ss, v2, stroke_id); + BKE_pbvh_bmesh_check_origdata(ss, v3, stroke_id); + + co1 = BM_ELEM_CD_PTR(v1, pbvh->cd_origco); + co2 = BM_ELEM_CD_PTR(v2, pbvh->cd_origco); + co3 = BM_ELEM_CD_PTR(v3, pbvh->cd_origco); + } + else { + co1 = v1->co; + co2 = v2->co; + co3 = v3->co; + } + + hit |= blender::bke::pbvh::ray_face_nearest_tri( + ray_start, ray_normal, co1, co2, co3, depth, dist_sq); } return hit; } -bool bmesh_node_nearest_to_ray(PBVHNode *node, - const float ray_start[3], - const float ray_normal[3], - float *depth, - float *dist_sq, - bool use_original) +struct UpdateNormalsTaskData { + PBVHNode *node; + Vector border_verts; + int cd_flag; + int cd_vert_node_offset; + int cd_face_node_offset; + int node_nr; +}; + +static void pbvh_update_normals_task_cb(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict /* tls */) { - bool hit = false; + UpdateNormalsTaskData *data = ((UpdateNormalsTaskData *)userdata) + n; + PBVHNode *node = data->node; + const int node_nr = data->node_nr; - if (use_original && node->bm_tot_ortri) { - for (int i = 0; i < node->bm_tot_ortri; i++) { - const int *t = node->bm_ortri[i]; - hit |= ray_face_nearest_tri(ray_start, - ray_normal, - node->bm_orco[t[0]], - node->bm_orco[t[1]], - node->bm_orco[t[2]], - depth, - dist_sq); + const int cd_vert_node_offset = data->cd_vert_node_offset; + + node->flag |= PBVH_UpdateCurvatureDir; + +#ifdef NORMAL_VERT_BAD +# undef NORMAL_VERT_BAD +#endif +#define NORMAL_VERT_BAD(v) \ + (!(v)->e || BM_ELEM_CD_GET_INT((v), cd_vert_node_offset) != node_nr || \ + ((*BM_ELEM_CD_PTR((v), data->cd_flag)) & SCULPTFLAG_PBVH_BOUNDARY)) + + for (BMVert *v : *node->bm_unique_verts) { + PBVH_CHECK_NAN(v->no); + + if (NORMAL_VERT_BAD(v)) { + data->border_verts.append(v); } - } - else { - for (BMFace *f : node->bm_faces) { - BLI_assert(f->len == 3); - if (!BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { - BMVert *v_tri[3]; - BM_face_as_array_vert_tri(f, v_tri); - hit |= ray_face_nearest_tri( - ray_start, ray_normal, v_tri[0]->co, v_tri[1]->co, v_tri[2]->co, depth, dist_sq); + zero_v3(v->no); + } + + for (BMVert *v : *node->bm_other_verts) { + PBVH_CHECK_NAN(v->no); + + if (NORMAL_VERT_BAD(v)) { + data->border_verts.append(v); + } + + zero_v3(v->no); + } + + for (BMFace *f : *node->bm_faces) { + BM_face_normal_update(f); + + PBVH_CHECK_NAN(f->no); + + BMLoop *l = f->l_first; + do { + PBVH_CHECK_NAN(l->v->no); + + if (!NORMAL_VERT_BAD(l->v)) { + add_v3_v3(l->v->no, f->no); } + } while ((l = l->next) != f->l_first); + } + + for (BMVert *v : *node->bm_unique_verts) { + PBVH_CHECK_NAN(v->no); + + if (!NORMAL_VERT_BAD(v)) { + normalize_v3(v->no); } } - return hit; + node->flag &= ~PBVH_UpdateNormals; } -void bmesh_normals_update(Span nodes) +void pbvh_bmesh_normals_update(PBVH *pbvh, Span nodes) { - for (PBVHNode *node : nodes) { - if (node->flag & PBVH_UpdateNormals) { - for (BMFace *face : node->bm_faces) { - BM_face_normal_update(face); + TaskParallelSettings settings; + Vector datas; + datas.resize(nodes.size()); + + for (int i : nodes.index_range()) { + datas[i].node = nodes[i]; + datas[i].node_nr = int(nodes[i] - &pbvh->nodes[0]); + datas[i].cd_flag = pbvh->cd_flag; + datas[i].cd_vert_node_offset = pbvh->cd_vert_node_offset; + datas[i].cd_face_node_offset = pbvh->cd_face_node_offset; + + BKE_pbvh_bmesh_check_tris(pbvh, nodes[i]); + } + + BKE_pbvh_parallel_range_settings(&settings, true, nodes.size()); + BLI_task_parallel_range(0, nodes.size(), datas.data(), pbvh_update_normals_task_cb, &settings); + + for (UpdateNormalsTaskData &data : datas) { + for (BMVert *v : data.border_verts) { + if (BM_elem_is_free((BMElem *)v, BM_VERT)) { + printf("%s: error, v was freed!\n", __func__); + continue; } - for (BMVert *vert : node->bm_unique_verts) { - BM_vert_normal_update(vert); + + if (!v->e || !v->e->l) { + continue; } - for (BMVert *vert : node->bm_other_verts) { - BM_vert_normal_update(vert); - } - node->flag &= ~PBVH_UpdateNormals; + + zero_v3(v->no); + + BMEdge *e = v->e; + do { + BMLoop *l = e->l; + if (!l) { + continue; + } + + do { + add_v3_v3(v->no, l->f->no); + } while ((l = l->radial_next) != e->l); + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + normalize_v3(v->no); } } } struct FastNodeBuildInfo { - int totface; /* Number of faces. */ - int start; /* Start of faces in array. */ - FastNodeBuildInfo *child1; - FastNodeBuildInfo *child2; + int totface; /* number of faces */ + int start; /* start of faces in array */ + int depth; + int node_index; + struct FastNodeBuildInfo *child1; + struct FastNodeBuildInfo *child2; + float cent[3], no[3]; + int tag; }; /** @@ -1950,51 +1708,51 @@ struct FastNodeBuildInfo { * to a sub part of the arrays. */ static void pbvh_bmesh_node_limit_ensure_fast(PBVH *pbvh, - const MutableSpan nodeinfo, - const Span> face_bounds, + BMFace **nodeinfo, + BBC *bbc_array, FastNodeBuildInfo *node, + Vector &leaves, MemArena *arena) { FastNodeBuildInfo *child1, *child2; - if (node->totface <= pbvh->leaf_limit) { + if (node->totface <= pbvh->leaf_limit || node->depth >= PBVH_STACK_FIXED_DEPTH) { return; } - /* Calculate bounding box around primitive centroids. */ - Bounds cb = negative_bounds(); + /* Calculate bounding box around primitive centroids */ + BB cb; + BB_reset(&cb); for (int i = 0; i < node->totface; i++) { BMFace *f = nodeinfo[i + node->start]; - const int face_index = BM_elem_index_get(f); - const float3 center = math::midpoint(face_bounds[face_index].min, face_bounds[face_index].max); - math::min_max(center, cb.min, cb.max); + BBC *bbc = &bbc_array[BM_elem_index_get(f)]; + + BB_expand(&cb, bbc->bcentroid); } - /* Initialize the children. */ + /* initialize the children */ - /* Find widest axis and its midpoint. */ - const int axis = math::dominant_axis(cb.max - cb.min); - const float mid = math::midpoint(cb.max[axis], cb.min[axis]); + /* Find widest axis and its midpoint */ + const int axis = BB_widest_axis(&cb); + const float mid = (cb.bmax[axis] + cb.bmin[axis]) * 0.5f; int num_child1 = 0, num_child2 = 0; - /* Split vertices along the middle line. */ + /* split vertices along the middle line */ const int end = node->start + node->totface; for (int i = node->start; i < end - num_child2; i++) { BMFace *f = nodeinfo[i]; - const int face_i = BM_elem_index_get(f); + BBC *bbc = &bbc_array[BM_elem_index_get(f)]; - if (math::midpoint(face_bounds[face_i].min[axis], face_bounds[face_i].max[axis]) > mid) { + if (bbc->bcentroid[axis] > mid) { int i_iter = end - num_child2 - 1; int candidate = -1; - /* Found a face that should be part of another node, look for a face to substitute with. */ + /* Found a face that should be part of another node, look for a face to substitute with. */ for (; i_iter > i; i_iter--) { BMFace *f_iter = nodeinfo[i_iter]; - const int face_iter_i = BM_elem_index_get(f_iter); - if (math::midpoint(face_bounds[face_iter_i].min[axis], - face_bounds[face_iter_i].max[axis]) <= mid) - { + const BBC *bbc_iter = &bbc_array[BM_elem_index_get(f_iter)]; + if (bbc_iter->bcentroid[axis] <= mid) { candidate = i_iter; break; } @@ -2006,13 +1764,15 @@ static void pbvh_bmesh_node_limit_ensure_fast(PBVH *pbvh, BMFace *tmp = nodeinfo[i]; nodeinfo[i] = nodeinfo[candidate]; nodeinfo[candidate] = tmp; + /* Increase both counts. */ num_child1++; num_child2++; } else { /* Not finding candidate means second half of array part is full of - * second node parts, just increase the number of child nodes for it. */ + * second node parts, just increase the number of child nodes for it. + */ num_child2++; } } @@ -2032,158 +1792,693 @@ static void pbvh_bmesh_node_limit_ensure_fast(PBVH *pbvh, } /* At this point, faces should have been split along the array range sequentially, - * each sequential part belonging to one node only. */ + * each sequential part belonging to one node only. + */ BLI_assert((num_child1 + num_child2) == node->totface); - node->child1 = child1 = static_cast( - BLI_memarena_alloc(arena, sizeof(FastNodeBuildInfo))); - node->child2 = child2 = static_cast( - BLI_memarena_alloc(arena, sizeof(FastNodeBuildInfo))); + node->child1 = child1 = (FastNodeBuildInfo *)BLI_memarena_alloc(arena, + sizeof(FastNodeBuildInfo)); + node->child2 = child2 = (FastNodeBuildInfo *)BLI_memarena_alloc(arena, + sizeof(FastNodeBuildInfo)); child1->totface = num_child1; child1->start = node->start; + child1->depth = node->depth + 1; + child2->totface = num_child2; child2->start = node->start + num_child1; + child2->depth = node->depth + 2; + child1->child1 = child1->child2 = child2->child1 = child2->child2 = nullptr; - pbvh_bmesh_node_limit_ensure_fast(pbvh, nodeinfo, face_bounds, child1, arena); - pbvh_bmesh_node_limit_ensure_fast(pbvh, nodeinfo, face_bounds, child2, arena); + pbvh_bmesh_node_limit_ensure_fast(pbvh, nodeinfo, bbc_array, child1, leaves, arena); + pbvh_bmesh_node_limit_ensure_fast(pbvh, nodeinfo, bbc_array, child2, leaves, arena); + + if (!child1->child1 && !child1->child2) { + leaves.append(child1); + } + + if (!child2->child1 && !child2->child2) { + leaves.append(child2); + } } -static void pbvh_bmesh_create_nodes_fast_recursive(PBVH *pbvh, - const Span nodeinfo, - const Span> face_bounds, - FastNodeBuildInfo *node, - int node_index) +struct LeafBuilderThreadData { + PBVH *pbvh; + BMFace **nodeinfo; + BBC *bbc_array; + Vector leaves; +}; + +ATTR_NO_OPT static void pbvh_bmesh_create_leaf_fast_task_cb(LeafBuilderThreadData *data, + const int i) { - PBVHNode *n = &pbvh->nodes[node_index]; - /* Two cases, node does not have children or does have children. */ - if (node->child1) { - int children_offset = pbvh->nodes.size(); + PBVH *pbvh = data->pbvh; + BMFace **nodeinfo = data->nodeinfo; + BBC *bbc_array = data->bbc_array; + struct FastNodeBuildInfo *node = data->leaves[i]; - n->children_offset = children_offset; - pbvh->nodes.resize(pbvh->nodes.size() + 2); - pbvh_bmesh_create_nodes_fast_recursive( - pbvh, nodeinfo, face_bounds, node->child1, children_offset); - pbvh_bmesh_create_nodes_fast_recursive( - pbvh, nodeinfo, face_bounds, node->child2, children_offset + 1); + /* node does not have children so it's a leaf node, populate with faces and tag accordingly + * this is an expensive part but it's not so easily thread-able due to vertex node indices */ + // const int cd_vert_node_offset = pbvh->cd_vert_node_offset; + const int cd_face_node_offset = pbvh->cd_face_node_offset; - n = &pbvh->nodes[node_index]; + PBVHNode *n = &pbvh->nodes[node->node_index]; + const int node_index = node->node_index; - /* Update bounding box. */ - n->vb = bounds::merge(pbvh->nodes[n->children_offset].vb, - pbvh->nodes[n->children_offset + 1].vb); - n->orig_vb = n->vb; - } - else { - /* Node does not have children so it's a leaf node, populate with faces and tag accordingly - * this is an expensive part but it's not so easily thread-able due to vertex node indices. */ - const int cd_vert_node_offset = pbvh->cd_vert_node_offset; - const int cd_face_node_offset = pbvh->cd_face_node_offset; + bool has_visible = false; - bool has_visible = false; + /* Build GPU buffers for new node */ - n->flag = PBVH_Leaf; - n->bm_faces.reserve(node->totface); + n->flag = PBVH_Leaf | PBVH_UpdateTris | PBVH_UpdateBB | PBVH_UpdateOriginalBB | + PBVH_UpdateTriAreas | PBVH_UpdateColor | PBVH_UpdateVisibility | + PBVH_UpdateDrawBuffers | PBVH_RebuildDrawBuffers | PBVH_UpdateCurvatureDir | + PBVH_UpdateMask | PBVH_UpdateRedraw; - n->vb = face_bounds[node->start]; + n->bm_faces = MEM_new>("bm_faces", node->totface); - const int end = node->start + node->totface; + /* Create vert hash sets */ + n->bm_unique_verts = MEM_new>("bm_unique_verts", node->totface * 3); + n->bm_other_verts = MEM_new>("bm_other_verts", node->totface * 3); - for (int i = node->start; i < end; i++) { - BMFace *f = nodeinfo[i]; + n->vb = negative_bounds(); - /* Update ownership of faces. */ - n->bm_faces.add_new(f); - BM_ELEM_CD_SET_INT(f, cd_face_node_offset, node_index); + const int end = node->start + node->totface; - /* Update vertices. */ - BMLoop *l_first = BM_FACE_FIRST_LOOP(f); - BMLoop *l_iter = l_first; - do { - BMVert *v = l_iter->v; - if (!n->bm_unique_verts.contains(v)) { - if (BM_ELEM_CD_GET_INT(v, cd_vert_node_offset) != DYNTOPO_NODE_NONE) { - n->bm_other_verts.add(v); - } - else { - n->bm_unique_verts.add(v); - BM_ELEM_CD_SET_INT(v, cd_vert_node_offset, node_index); - } + for (int i = node->start; i < end; i++) { + BMFace *f = nodeinfo[i]; + BBC *bbc = &bbc_array[BM_elem_index_get(f)]; + + /* Update ownership of faces */ + + n->bm_faces->add(f); + BM_ELEM_CD_SET_INT(f, cd_face_node_offset, node_index); + + /* Update vertices */ + BMLoop *l_first = BM_FACE_FIRST_LOOP(f); + BMLoop *l_iter = l_first; + do { + BMVert *v = l_iter->v; + + int old = BM_ELEM_CD_GET_INT(v, pbvh->cd_vert_node_offset); + ; + int32_t *ptr = static_cast( + POINTER_OFFSET(v->head.data, pbvh->cd_vert_node_offset)); + + if (BM_ELEM_CD_GET_INT(v, pbvh->cd_vert_node_offset) == DYNTOPO_NODE_NONE) { + if (old == DYNTOPO_NODE_NONE && + atomic_cas_int32(ptr, DYNTOPO_NODE_NONE, node_index) == DYNTOPO_NODE_NONE) + { + n->bm_unique_verts->add(v); + } + else { + n->bm_other_verts->add(v); } - /* Update node bounding box. */ - } while ((l_iter = l_iter->next) != l_first); - - if (!BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { - has_visible = true; } + else if (old != node->node_index) { + n->bm_other_verts->add(v); + } + } while ((l_iter = l_iter->next) != l_first); - n->vb = bounds::merge(n->vb, face_bounds[BM_elem_index_get(f)]); + /* Update node bounding box */ + if (!pbvh_poly_hidden(pbvh, f)) { + has_visible = true; } - BLI_assert(n->vb.min[0] <= n->vb.max[0] && n->vb.min[1] <= n->vb.max[1] && - n->vb.min[2] <= n->vb.max[2]); + n->vb = bounds::merge(n->vb, *(Bounds *)bbc); + } + BLI_assert(n->vb.bmin[0] <= n->vb.bmax[0] && n->vb.bmin[1] <= n->vb.bmax[1] && + n->vb.bmin[2] <= n->vb.bmax[2]); + + n->orig_vb = n->vb; + + BKE_pbvh_node_fully_hidden_set(n, !has_visible); + n->flag |= PBVH_UpdateNormals | PBVH_UpdateCurvatureDir; +} + +ATTR_NO_OPT static void pbvh_bmesh_create_nodes_fast_recursive_create( + PBVH *pbvh, BMFace **nodeinfo, BBC *bbc_array, struct FastNodeBuildInfo *node) +{ + if (node->child1) { + int children_offset = pbvh->nodes.size(); + pbvh->nodes.resize(pbvh->nodes.size() + 2); + + PBVHNode *n = &pbvh->nodes[node->node_index]; + n->children_offset = children_offset; + + n->depth = node->depth; + (n + 1)->depth = node->child1->depth; + (n + 2)->depth = node->child2->depth; + + node->child1->node_index = children_offset; + node->child2->node_index = children_offset + 1; + + pbvh_bmesh_create_nodes_fast_recursive_create(pbvh, nodeinfo, bbc_array, node->child1); + pbvh_bmesh_create_nodes_fast_recursive_create(pbvh, nodeinfo, bbc_array, node->child2); + } +} + +ATTR_NO_OPT static void pbvh_bmesh_create_nodes_fast_recursive_final( + PBVH *pbvh, BMFace **nodeinfo, BBC *bbc_array, struct FastNodeBuildInfo *node) +{ + /* two cases, node does not have children or does have children */ + if (node->child1) { + pbvh_bmesh_create_nodes_fast_recursive_final(pbvh, nodeinfo, bbc_array, node->child1); + pbvh_bmesh_create_nodes_fast_recursive_final(pbvh, nodeinfo, bbc_array, node->child2); + + PBVHNode *n = &pbvh->nodes[node->node_index]; + + /* Update bounding box */ + n->vb = bounds::merge(pbvh->nodes[node->child1->node_index].vb, + pbvh->nodes[node->child2->node_index].vb); n->orig_vb = n->vb; - - /* Build GPU buffers for new node and update vertex normals. */ - BKE_pbvh_node_mark_rebuild_draw(n); - - BKE_pbvh_node_fully_hidden_set(n, !has_visible); - n->flag |= PBVH_UpdateNormals; } } /***************************** Public API *****************************/ -void update_bmesh_offsets(PBVH *pbvh, int cd_vert_node_offset, int cd_face_node_offset) +static bool test_colinear_tri(BMFace *f) { - pbvh->cd_vert_node_offset = cd_vert_node_offset; - pbvh->cd_face_node_offset = cd_face_node_offset; + BMLoop *l = f->l_first; + + float area_limit = 0.00001f; + area_limit = len_squared_v3v3(l->v->co, l->next->v->co) * 0.001; + + return area_tri_v3(l->v->co, l->next->v->co, l->prev->v->co) <= area_limit; } -PBVH *build_bmesh(BMesh *bm, - BMLog *log, - const int cd_vert_node_offset, - const int cd_face_node_offset) +namespace blender::bke::pbvh { + +float test_sharp_faces_bmesh(BMFace *f1, BMFace *f2, float limit) { - std::unique_ptr pbvh = std::make_unique(); - pbvh->header.type = PBVH_BMESH; + float angle = math::safe_acos(dot_v3v3(f1->no, f2->no)); + + /* Detect coincident triangles. */ + if (f1->len == 3 && test_colinear_tri(f1)) { + return false; + } + if (f2->len == 3 && test_colinear_tri(f2)) { + return false; + } + + /* Try to ignore folded over edges. */ + if (angle > M_PI * 0.6) { + return false; + } + + return angle > limit; +} + +static void update_edge_boundary_bmesh_uv(BMEdge *e, int cd_edge_boundary, const CustomData *ldata) +{ + int base = ldata->typemap[CD_PROP_FLOAT2]; + + *BM_ELEM_CD_PTR(e, cd_edge_boundary) &= ~(SCULPT_BOUNDARY_UPDATE_UV | SCULPT_BOUNDARY_UV); + + for (int i = base; i < ldata->totlayer; i++) { + const CustomDataLayer &layer = ldata->layers[i]; + + if (layer.type != CD_PROP_FLOAT2) { + break; + } + if (layer.flag & (CD_FLAG_TEMPORARY | CD_FLAG_ELEM_NOINTERP)) { + continue; + } + + BMLoop *l = e->l; + + int cd_uv = layer.offset; + float limit = sculpt::calc_uv_snap_limit(l, cd_uv); + limit *= limit; + + float2 a1 = BM_ELEM_CD_PTR((l->v == e->v1 ? l : l->next), cd_uv); + float2 a2 = BM_ELEM_CD_PTR((l->v == e->v2 ? l : l->next), cd_uv); + + do { + float *b1 = BM_ELEM_CD_PTR((l->v == e->v1 ? l : l->next), cd_uv); + float *b2 = BM_ELEM_CD_PTR((l->v == e->v2 ? l : l->next), cd_uv); + + if (len_squared_v2v2(a1, b1) > limit || len_squared_v2v2(a2, b2) > limit) { + *BM_ELEM_CD_PTR(e, cd_edge_boundary) |= SCULPT_BOUNDARY_UV; + return; + } + } while ((l = l->radial_next) != e->l); + } +} + +void update_edge_boundary_bmesh(BMEdge *e, + int cd_faceset_offset, + int cd_edge_boundary, + const int /*cd_flag*/, + const int /*cd_valence*/, + const CustomData *ldata, + float sharp_angle_limit) +{ + int oldflag = BM_ELEM_CD_GET_INT(e, cd_edge_boundary); + + if (e->l && (oldflag & SCULPT_BOUNDARY_UPDATE_UV) && ldata->typemap[CD_PROP_FLOAT2] != -1) { + update_edge_boundary_bmesh_uv(e, cd_edge_boundary, ldata); + oldflag = BM_ELEM_CD_GET_INT(e, cd_edge_boundary); + } + +#if 0 + if (BM_ELEM_CD_GET_INT(e, cd_edge_boundary) & SCULPT_BOUNDARY_UV) { + e->head.hflag |= BM_ELEM_SELECT; + } + else { + e->head.hflag &= ~BM_ELEM_SELECT; + } +#endif + + if (oldflag & SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE) { + if (e->l && e->l != e->l->radial_next && + test_sharp_faces_bmesh(e->l->f, e->l->radial_next->f, sharp_angle_limit)) + { + *BM_ELEM_CD_PTR(e, cd_edge_boundary) |= SCULPT_BOUNDARY_SHARP_ANGLE; + } + else { + *BM_ELEM_CD_PTR(e, cd_edge_boundary) &= ~SCULPT_BOUNDARY_SHARP_ANGLE; + } + oldflag = BM_ELEM_CD_GET_INT(e, cd_edge_boundary); + } + + if (!(oldflag & SCULPT_BOUNDARY_NEEDS_UPDATE)) { + return; + } + + int newflag = oldflag & (SCULPT_BOUNDARY_UV | SCULPT_BOUNDARY_SHARP_ANGLE); + + if (!e->l || e->l == e->l->radial_next) { + newflag |= SCULPT_BOUNDARY_MESH; + } + + if (e->l && e->l != e->l->radial_next && cd_faceset_offset != -1) { + int fset1 = BM_ELEM_CD_GET_INT(e->l->f, cd_faceset_offset); + int fset2 = BM_ELEM_CD_GET_INT(e->l->radial_next->f, cd_faceset_offset); + + newflag |= fset1 != fset2 ? SCULPT_BOUNDARY_FACE_SET : 0; + } + + newflag |= !BM_elem_flag_test(e, BM_ELEM_SMOOTH) ? SCULPT_BOUNDARY_SHARP_MARK : 0; + newflag |= BM_elem_flag_test(e, BM_ELEM_SEAM) ? SCULPT_BOUNDARY_SEAM : 0; + + *BM_ELEM_CD_PTR(e, cd_edge_boundary) = newflag; +} +} // namespace blender::bke::pbvh + +static void pbvh_bmesh_update_uv_boundary_intern(BMVert *v, + int cd_boundary, + int cd_uv, + const CustomData *ldata) +{ + int *boundflag = BM_ELEM_CD_PTR(v, cd_boundary); + + if (!v->e) { + return; + } + + BMEdge *e = v->e; + do { + BMLoop *l = e->l; + if (!l) { + continue; + } + + float snap_limit = blender::bke::sculpt::calc_uv_snap_limit(l, cd_uv); + float snap_limit_sqr = snap_limit * snap_limit; + + float2 uv1a = *BM_ELEM_CD_PTR(l->v == v ? l : l->next, cd_uv); + float2 uv1b = *BM_ELEM_CD_PTR(l->v == v ? l->next : l, cd_uv); + + do { + float2 uv2a = *BM_ELEM_CD_PTR(l->v == v ? l : l->next, cd_uv); + float2 uv2b = *BM_ELEM_CD_PTR(l->v == v ? l->next : l, cd_uv); + + if (len_squared_v2v2(uv1a, uv2a) > snap_limit_sqr || + len_squared_v2v2(uv1b, uv2b) > snap_limit_sqr) + { + *boundflag |= SCULPT_BOUNDARY_UV; + } + + /* Corners are calculated from the number of distinct charts. */ + BMLoop *l2 = l->v == v ? l : l->next; + if (blender::bke::sculpt::loop_is_corner(l2, cd_uv, snap_limit, ldata)) { + *boundflag |= SCULPT_CORNER_UV; + *boundflag |= SCULPT_BOUNDARY_UV; + } + + } while ((l = l->radial_next) != e->l); + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); +} + +static void pbvh_bmesh_update_uv_boundary(BMVert *v, int cd_boundary, const CustomData *ldata) +{ + int base_uv = ldata->typemap[CD_PROP_FLOAT2]; + + *BM_ELEM_CD_PTR(v, cd_boundary) &= ~(SCULPT_BOUNDARY_UPDATE_UV | SCULPT_BOUNDARY_UV | + SCULPT_CORNER_UV); + + if (base_uv == -1) { + return; + } + + for (int i = base_uv; i < ldata->totlayer; i++) { + if (ldata->layers[i].type != CD_PROP_FLOAT2) { + break; + } + if (ldata->layers[i].flag & (CD_FLAG_TEMPORARY | CD_FLAG_ELEM_NOINTERP)) { + continue; + } + + pbvh_bmesh_update_uv_boundary_intern(v, cd_boundary, ldata->layers[i].offset, ldata); + } +} + +namespace blender::bke::pbvh { +void update_vert_boundary_bmesh(int cd_faceset_offset, + int cd_vert_node_offset, + int cd_face_node_offset, + int /*cd_vcol*/, + int cd_boundary_flag, + const int cd_flag, + const int cd_valence, + BMVert *v, + const CustomData *ldata, + float sharp_angle_limit) +{ + int newflag = *BM_ELEM_CD_PTR(v, cd_flag); + newflag &= ~(SCULPTFLAG_VERT_FSET_HIDDEN | SCULPTFLAG_PBVH_BOUNDARY); + + bool update_sharp_angle = BM_ELEM_CD_GET_INT(v, cd_boundary_flag) & + SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE; + + if (BM_ELEM_CD_GET_INT(v, cd_boundary_flag) & SCULPT_BOUNDARY_UPDATE_UV) { + pbvh_bmesh_update_uv_boundary(v, cd_boundary_flag, ldata); + } + +#if 0 + if (BM_ELEM_CD_GET_INT(v, cd_boundary_flag) & SCULPT_BOUNDARY_UV) { + v->head.hflag |= BM_ELEM_SELECT; + } + else { + v->head.hflag &= ~BM_ELEM_SELECT; + } +#endif + + int boundflag = BM_ELEM_CD_GET_INT(v, cd_boundary_flag) & + (SCULPT_BOUNDARY_UV | SCULPT_CORNER_UV); + + BMEdge *e = v->e; + if (!e) { + boundflag |= SCULPT_BOUNDARY_MESH; + + BM_ELEM_CD_SET_INT(v, cd_valence, 0); + BM_ELEM_CD_SET_INT(v, cd_boundary_flag, boundflag); + *BM_ELEM_CD_PTR(v, cd_flag) = newflag; + + return; + } + + int val = 0; + + int ni = BM_ELEM_CD_GET_INT(v, cd_vert_node_offset); + + int sharpcount = 0; + int seamcount = 0; + int quadcount = 0; + + Vector fsets; + + int sharp_angle_num = 0; + do { + BMVert *v2 = v == e->v1 ? e->v2 : e->v1; + + if (BM_ELEM_CD_GET_INT(v2, cd_vert_node_offset) != ni) { + newflag |= SCULPTFLAG_PBVH_BOUNDARY; + } + + if (update_sharp_angle) { + if (e->l && e->l != e->l->radial_next) { + if (test_sharp_faces_bmesh(e->l->f, e->l->radial_next->f, sharp_angle_limit)) { + boundflag |= SCULPT_BOUNDARY_SHARP_ANGLE; + sharp_angle_num++; + } + } + } + + if (e->head.hflag & BM_ELEM_SEAM) { + boundflag |= SCULPT_BOUNDARY_SEAM; + seamcount++; + + if (seamcount > 2) { + boundflag |= SCULPT_CORNER_SEAM; + } + } + + if (!(e->head.hflag & BM_ELEM_SMOOTH)) { + boundflag |= SCULPT_BOUNDARY_SHARP_MARK; + sharpcount++; + } + + if (e->l) { + if ((e->l->f->head.hflag & BM_ELEM_HIDDEN) || + (e->l->radial_next->f->head.hflag & BM_ELEM_HIDDEN)) + { + newflag |= SCULPTFLAG_VERT_FSET_HIDDEN; + } + + if (BM_ELEM_CD_GET_INT(e->l->f, cd_face_node_offset) != ni) { + newflag |= SCULPTFLAG_PBVH_BOUNDARY; + } + + if (e->l != e->l->radial_next) { + if (e->l->f->len > 3) { + quadcount++; + } + + if (e->l->radial_next->f->len > 3) { + quadcount++; + } + + if (BM_ELEM_CD_GET_INT(e->l->radial_next->f, cd_face_node_offset) != ni) { + newflag |= SCULPTFLAG_PBVH_BOUNDARY; + } + } + + if (e->l->f->len > 3) { + newflag |= SCULPTFLAG_NEED_TRIANGULATE; + } + + if (cd_faceset_offset != -1) { + int fset = BM_ELEM_CD_GET_INT(e->l->f, cd_faceset_offset); + if (!fsets.contains(fset)) { + fsets.append(fset); + } + } + + /* Also check e->l->radial_next, in case we are not manifold + * which can mess up the loop order + */ + if (e->l->radial_next != e->l) { + if (cd_faceset_offset != -1) { + int fset = BM_ELEM_CD_GET_INT(e->l->radial_next->f, cd_faceset_offset); + + if (!fsets.contains(fset)) { + fsets.append(fset); + } + } + + if (e->l->radial_next->f->len > 3) { + newflag |= SCULPTFLAG_NEED_TRIANGULATE; + } + } + } + + if (e->l && e->l->radial_next == e->l) { + boundflag |= SCULPT_BOUNDARY_MESH; + } + + val++; + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + if (fsets.size() > 1) { + boundflag |= SCULPT_BOUNDARY_FACE_SET; + } + + if (fsets.size() > 2) { + boundflag |= SCULPT_CORNER_FACE_SET; + } + + if (sharp_angle_num > 2) { + boundflag |= SCULPT_CORNER_SHARP_ANGLE; + } + + if (sharpcount > 2) { + boundflag |= SCULPT_CORNER_SHARP_MARK; + } + + if (seamcount > 2) { + boundflag |= SCULPT_CORNER_SEAM; + } + + if ((boundflag & SCULPT_BOUNDARY_MESH) && quadcount >= 3) { + boundflag |= SCULPT_CORNER_MESH; + } + + BM_ELEM_CD_SET_INT(v, cd_boundary_flag, boundflag); + BM_ELEM_CD_SET_INT(v, cd_valence, val); + *(BM_ELEM_CD_PTR(v, cd_flag)) = newflag; +} + +void sharp_limit_set(PBVH *pbvh, float limit) +{ + pbvh->sharp_angle_limit = limit; +} + +} // namespace blender::bke::pbvh + +/*Used by symmetrize to update boundary flags*/ +void BKE_pbvh_recalc_bmesh_boundary(PBVH *pbvh) +{ + BMVert *v; + BMIter iter; + + BM_ITER_MESH (v, &iter, pbvh->header.bm, BM_VERTS_OF_MESH) { + blender::bke::pbvh::update_vert_boundary_bmesh(pbvh->cd_faceset_offset, + pbvh->cd_vert_node_offset, + pbvh->cd_face_node_offset, + pbvh->cd_vcol_offset, + pbvh->cd_boundary_flag, + pbvh->cd_flag, + pbvh->cd_valence, + v, + &pbvh->header.bm->ldata, + pbvh->sharp_angle_limit); + } +} + +namespace blender::bke::pbvh { +void set_idmap(PBVH *pbvh, BMIdMap *idmap) +{ + pbvh->bm_idmap = idmap; +} +} // namespace blender::bke::pbvh + +#if 0 +PBVH *global_debug_pbvh = nullptr; + +void debug_pbvh_on_vert_kill(BMVert *v) +{ + PBVH *pbvh = global_debug_pbvh; + + if (!pbvh) { + return; + } + + for (int i = 0; i < pbvh->nodes.size(); i++) { + PBVHNode &node = pbvh->nodes[i]; + + if (!(node.flag & PBVH_Leaf)) { + continue; + } + + for (BMVert *v2 : *node.bm_unique_verts) { + if (v2 == v) { + printf("Error! Vertex %p is still in bm_unique_verts\n", v); + } + } + for (BMVert *v2 : *node.bm_other_verts) { + if (v2 == v) { + printf("Error! Vertex still in bm_other_verts\n"); + } + } + } +} +#endif + +namespace blender::bke::pbvh { + +/* Build a PBVH from a BMesh */ +void build_bmesh(PBVH *pbvh, + Mesh *me, + BMesh *bm, + BMLog *log, + BMIdMap *idmap, + const int cd_vert_node_offset, + const int cd_face_node_offset, + const int cd_face_areas, + const int cd_boundary_flag, + const int cd_edge_boundary, + const int cd_flag_offset, + const int cd_valence_offset, + const int cd_origco, + const int cd_origno) +{ + pbvh->bm_idmap = idmap; + pbvh->header.bm = bm; + + pbvh->cd_face_area = cd_face_areas; + pbvh->cd_vert_node_offset = cd_vert_node_offset; + pbvh->cd_face_node_offset = cd_face_node_offset; + pbvh->cd_vert_mask_offset = CustomData_get_offset_named( + &bm->vdata, CD_PROP_FLOAT, ".sculpt_mask"); + pbvh->cd_boundary_flag = cd_boundary_flag; + pbvh->cd_edge_boundary = cd_edge_boundary; + pbvh->cd_origco = cd_origco; + pbvh->cd_origno = cd_origno; + + pbvh->cd_flag = cd_flag_offset; + pbvh->cd_valence = cd_valence_offset; + + pbvh->mesh = me; pbvh->header.bm = bm; - BKE_pbvh_bmesh_detail_size_set(pbvh.get(), 0.75); + dyntopo::detail_size_set(pbvh, 0.75f, 0.4f); pbvh->header.type = PBVH_BMESH; pbvh->bm_log = log; + pbvh->cd_faceset_offset = CustomData_get_offset_named( + &pbvh->header.bm->pdata, CD_PROP_INT32, ".sculpt_face_set"); - /* TODO: choose leaf limit better. */ - pbvh->leaf_limit = 400; + int tottri = poly_to_tri_count(bm->totface, bm->totloop); - pbvh::update_bmesh_offsets(pbvh.get(), cd_vert_node_offset, cd_face_node_offset); - - if (bm->totface == 0) { - return {}; + /* TODO: choose leaf limit better */ + if (tottri > 4 * 1000 * 1000) { + pbvh->leaf_limit = 10000; + } + else { + pbvh->leaf_limit = 1000; } - /* bounding box array of all faces, no need to recalculate every time. */ - Array> face_bounds(bm->totface); - Array nodeinfo(bm->totface); + BMIter iter; + BMVert *v; + + /* bounding box array of all faces, no need to recalculate every time */ + BBC *bbc_array = MEM_cnew_array(bm->totface, "BBC"); + BMFace **nodeinfo = MEM_cnew_array(bm->totface, "nodeinfo"); MemArena *arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, "fast PBVH node storage"); - BMIter iter; BMFace *f; int i; BM_ITER_MESH_INDEX (f, &iter, bm, BM_FACES_OF_MESH, i) { - face_bounds[i] = negative_bounds(); - + BBC *bbc = &bbc_array[i]; BMLoop *l_first = BM_FACE_FIRST_LOOP(f); BMLoop *l_iter = l_first; - do { - math::min_max(float3(l_iter->v->co), face_bounds[i].min, face_bounds[i].max); - } while ((l_iter = l_iter->next) != l_first); - /* so we can do direct lookups on 'face_bounds' */ + /* Check for currupted faceset. */ + if (pbvh->cd_faceset_offset != -1 && BM_ELEM_CD_GET_INT(f, pbvh->cd_faceset_offset) == 0) { + BM_ELEM_CD_SET_INT(f, pbvh->cd_faceset_offset, 1); + } + + BB_reset((BB *)bbc); + do { + BB_expand((BB *)bbc, l_iter->v->co); + } while ((l_iter = l_iter->next) != l_first); + BBC_update_centroid(bbc); + + /* so we can do direct lookups on 'bbc_array' */ BM_elem_index_set(f, i); /* set_dirty! */ nodeinfo[i] = f; BM_ELEM_CD_SET_INT(f, cd_face_node_offset, DYNTOPO_NODE_NONE); @@ -2191,442 +2486,1432 @@ PBVH *build_bmesh(BMesh *bm, /* Likely this is already dirty. */ bm->elem_index_dirty |= BM_FACE; - BMVert *v; BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { BM_ELEM_CD_SET_INT(v, cd_vert_node_offset, DYNTOPO_NODE_NONE); } - /* Set up root node. */ - FastNodeBuildInfo rootnode = {0}; + /* setup root node */ + struct FastNodeBuildInfo rootnode = {0}; + + Vector leaves; + rootnode.totface = bm->totface; + int totleaf = 0; - /* Start recursion, assign faces to nodes accordingly. */ - pbvh_bmesh_node_limit_ensure_fast(pbvh.get(), nodeinfo, face_bounds, &rootnode, arena); + /* start recursion, assign faces to nodes accordingly */ + pbvh_bmesh_node_limit_ensure_fast(pbvh, nodeinfo, bbc_array, &rootnode, leaves, arena); - /* We now have all faces assigned to a node, - * next we need to assign those to the gsets of the nodes. */ + pbvh->nodes.resize(pbvh->nodes.size() + 1); + rootnode.node_index = 0; - /* Start with all faces in the root node. */ - pbvh->nodes.append({}); + pbvh_bmesh_create_nodes_fast_recursive_create(pbvh, nodeinfo, bbc_array, &rootnode); + totleaf = leaves.size(); - /* Take root node and visit and populate children recursively. */ - pbvh_bmesh_create_nodes_fast_recursive(pbvh.get(), nodeinfo, face_bounds, &rootnode, 0); + if (!totleaf) { + leaves.append(&rootnode); + totleaf = 1; + } + + /* build leaf nodes */ + LeafBuilderThreadData tdata; + tdata.pbvh = pbvh; + tdata.nodeinfo = nodeinfo; + tdata.bbc_array = bbc_array; + tdata.leaves = leaves; + + for (int i : IndexRange(totleaf)) { + pbvh_bmesh_create_leaf_fast_task_cb(&tdata, i); + } + + /* take root node and visit and populate children recursively */ + pbvh_bmesh_create_nodes_fast_recursive_final(pbvh, nodeinfo, bbc_array, &rootnode); BLI_memarena_free(arena); - return pbvh.release(); -} + MEM_freeN(bbc_array); + MEM_freeN(nodeinfo); -bool bmesh_update_topology(PBVH *pbvh, - PBVHTopologyUpdateMode mode, - const float center[3], - const float view_normal[3], - float radius, - const bool use_frontface, - const bool use_projected) -{ - const int cd_vert_mask_offset = CustomData_get_offset_named( - &pbvh->header.bm->vdata, CD_PROP_FLOAT, ".sculpt_mask"); - const int cd_vert_node_offset = pbvh->cd_vert_node_offset; - const int cd_face_node_offset = pbvh->cd_face_node_offset; + if (me) { /* Ensure pbvh->vcol_type, vcol_domain and cd_vcol_offset are up to date. */ + CustomDataLayer *cl; + AttrDomain domain; - bool modified = false; - - if (view_normal) { - BLI_assert(len_squared_v3(view_normal) != 0.0f); + BKE_pbvh_get_color_layer(pbvh, me, &cl, &domain); } - if (mode & PBVH_Collapse) { - EdgeQueue q; - BLI_mempool *queue_pool = BLI_mempool_create(sizeof(BMVert *) * 2, 0, 128, BLI_MEMPOOL_NOP); - EdgeQueueContext eq_ctx = { - &q, - queue_pool, - pbvh->header.bm, - cd_vert_mask_offset, - cd_vert_node_offset, - cd_face_node_offset, - }; + /*final check that nodes are sufficiently subdivided*/ + int totnode = pbvh->nodes.size(); - short_edge_queue_create( - &eq_ctx, pbvh, center, view_normal, radius, use_frontface, use_projected); - modified |= pbvh_bmesh_collapse_short_edges(&eq_ctx, pbvh); - BLI_heapsimple_free(q.heap, nullptr); - BLI_mempool_destroy(queue_pool); - } + for (int i = 0; i < totnode; i++) { + PBVHNode *n = &pbvh->nodes[i]; - if (mode & PBVH_Subdivide) { - EdgeQueue q; - BLI_mempool *queue_pool = BLI_mempool_create(sizeof(BMVert *) * 2, 0, 128, BLI_MEMPOOL_NOP); - EdgeQueueContext eq_ctx = { - &q, - queue_pool, - pbvh->header.bm, - cd_vert_mask_offset, - cd_vert_node_offset, - cd_face_node_offset, - }; - - long_edge_queue_create( - &eq_ctx, pbvh, center, view_normal, radius, use_frontface, use_projected); - modified |= pbvh_bmesh_subdivide_long_edges(&eq_ctx, pbvh); - BLI_heapsimple_free(q.heap, nullptr); - BLI_mempool_destroy(queue_pool); - } - - /* Unmark nodes. */ - for (PBVHNode &node : pbvh->nodes) { - if (node.flag & PBVH_Leaf && node.flag & PBVH_UpdateTopology) { - node.flag &= ~PBVH_UpdateTopology; - } - } - - /* Go over all changed nodes and check if anything needs to be updated. */ - for (PBVHNode &node : pbvh->nodes) { - if (node.flag & PBVH_Leaf && node.flag & PBVH_TopologyUpdated) { - node.flag &= ~PBVH_TopologyUpdated; - - if (node.bm_ortri) { - /* Reallocate original triangle data. */ - pbvh_bmesh_node_drop_orig(&node); - BKE_pbvh_bmesh_node_save_orig(pbvh->header.bm, pbvh->bm_log, &node, true); - } - } - } - -#ifdef USE_VERIFY - pbvh_bmesh_verify(pbvh); + if (totnode != pbvh->nodes.size()) { +#ifdef PROXY_ADVANCED + BKE_pbvh_free_proxyarray(pbvh, n); #endif + } - return modified; + if (n->flag & PBVH_Leaf) { + /* Recursively split nodes that have gotten too many + * elements */ + pbvh_bmesh_node_limit_ensure(pbvh, i); + } + } + + pbvh_print_mem_size(pbvh); + + /* Update boundary flags. */ + BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { + update_vert_boundary_bmesh(pbvh->cd_faceset_offset, + pbvh->cd_vert_node_offset, + pbvh->cd_face_node_offset, + -1, + pbvh->cd_boundary_flag, + pbvh->cd_flag, + pbvh->cd_valence, + v, + &bm->ldata, + pbvh->sharp_angle_limit); + } + + /* update face areas */ + const int cd_face_area = pbvh->cd_face_area; + for (int i = 0; i < pbvh->nodes.size(); i++) { + PBVHNode *node = &pbvh->nodes[i]; + + if (!(node->flag & PBVH_Leaf)) { + continue; + } + + BKE_pbvh_bmesh_check_tris(pbvh, node); + + node->flag |= PBVH_UpdateTriAreas; + BKE_pbvh_check_tri_areas(pbvh, node); + + int area_src_i = pbvh->face_area_i ^ 1; + int area_dst_i = pbvh->face_area_i; + + /* make sure read side of double buffer is set too */ + for (BMFace *f : *node->bm_faces) { + float *areabuf = BM_ELEM_CD_PTR(f, cd_face_area); + areabuf[area_dst_i] = areabuf[area_src_i]; + } + } + + pbvh_bmesh_check_nodes(pbvh); } } // namespace blender::bke::pbvh -/* Updates a given PBVH Node with the original coordinates of the corresponding BMesh vertex. - * Attempts to retrieve the value from the BMLog, falls back to the vertex's current coordinates - * if it is either not found in the log or not requested. */ -static void BKE_pbvh_bmesh_node_copy_original_co( - BMLog *log, PBVHNode *node, BMVert *v, int i, bool use_original) +void BKE_pbvh_set_bm_log(PBVH *pbvh, BMLog *log) { - if (!use_original) { - copy_v3_v3(node->bm_orco[i], v->co); - } - else { - const float *origco = BM_log_find_original_vert_co(log, v); - if (origco) { - copy_v3_v3(node->bm_orco[i], origco); - } - else { - copy_v3_v3(node->bm_orco[i], v->co); - } - } - - node->bm_orvert[i] = v; - BM_elem_index_set(v, i); /* set_dirty! */ + pbvh->bm_log = log; + BM_log_set_idmap(log, pbvh->bm_idmap); } -void BKE_pbvh_bmesh_node_save_orig(BMesh *bm, BMLog *log, PBVHNode *node, bool use_original) +namespace blender::bke::dyntopo { +bool remesh_topology_nodes(blender::bke::dyntopo::BrushTester *brush_tester, + Object *ob, + PBVH *pbvh, + bool (*searchcb)(const PBVHNode &node, + const float3 &location, + const float radius_sq, + const bool original), + void (*undopush)(PBVHNode *node, void *data), + const blender::float3 &location, + const float radius_sq, + const bool original, + PBVHTopologyUpdateMode mode, + const bool use_frontface, + float3 view_normal, + bool updatePBVH, + DyntopoMaskCB mask_cb, + void *mask_cb_data, + float quality, + void *searchdata) { - /* Skip if original coords/triangles are already saved. */ - if (node->bm_orco) { + bool modified = false; + Vector nodes; + + for (int i = 0; i < pbvh->nodes.size(); i++) { + PBVHNode *node = &pbvh->nodes[i]; + + if (!(node->flag & PBVH_Leaf) || !searchcb(*node, location, radius_sq, original)) { + continue; + } + + if (node->flag & PBVH_Leaf) { + undopush(node, searchdata); + + nodes.append(node); + } + } + + for (PBVHNode *node : nodes) { + node->flag |= PBVH_UpdateCurvatureDir; + BKE_pbvh_node_mark_topology_update(node); + } + + modified = remesh_topology(brush_tester, + ob, + pbvh, + mode, + use_frontface, + view_normal, + updatePBVH, + mask_cb, + mask_cb_data, + quality); + + return modified; +} +} // namespace blender::bke::dyntopo + +PBVHTriBuf *BKE_pbvh_bmesh_get_tris(PBVH *pbvh, PBVHNode *node) +{ + BKE_pbvh_bmesh_check_tris(pbvh, node); + + return node->tribuf; +} + +void BKE_pbvh_bmesh_free_tris(PBVH * /*pbvh*/, PBVHNode *node) +{ + if (node->tribuf) { + MEM_delete(node->tribuf); + node->tribuf = nullptr; + } + + if (node->tri_buffers) { + MEM_delete>(node->tri_buffers); + node->tri_buffers = nullptr; + } +} + +static inline void pbvh_tribuf_add_vert(PBVHTriBuf *tribuf, PBVHVertRef vertex, BMLoop *l) +{ + tribuf->verts.append(vertex); + tribuf->loops.append((uintptr_t)l); +} + +static inline void pbvh_tribuf_add_edge(PBVHTriBuf *tribuf, int v1, int v2) +{ + tribuf->edges.append(v1); + tribuf->edges.append(v2); +} + +void pbvh_bmesh_check_other_verts(PBVHNode *node) +{ + if (!(node->flag & PBVH_UpdateOtherVerts)) { return; } - const int totvert = node->bm_unique_verts.size() + node->bm_other_verts.size(); + node->flag &= ~PBVH_UpdateOtherVerts; - const int tottri = node->bm_faces.size(); + if (node->bm_other_verts) { + MEM_delete(node->bm_other_verts); + } - node->bm_orco = static_cast( - MEM_mallocN(sizeof(*node->bm_orco) * totvert, __func__)); - node->bm_ortri = static_cast(MEM_mallocN(sizeof(*node->bm_ortri) * tottri, __func__)); - node->bm_orvert = static_cast( - MEM_mallocN(sizeof(*node->bm_orvert) * totvert, __func__)); + node->bm_other_verts = MEM_new>("bm_other_verts"); - /* Copy out the vertices and assign a temporary index. */ + for (BMFace *f : *node->bm_faces) { + BMLoop *l = f->l_first; + + do { + if (!node->bm_unique_verts->contains(l->v)) { + node->bm_other_verts->add(l->v); + } + } while ((l = l->next) != f->l_first); + } +} + +static void pbvh_init_tribuf(PBVHNode * /*node*/, PBVHTriBuf *tribuf) +{ + tribuf->mat_nr = 0; + + tribuf->edges.clear(); + tribuf->verts.clear(); + tribuf->tris.clear(); + tribuf->loops.clear(); + + tribuf->edges.reserve(512); + tribuf->verts.reserve(512); + tribuf->tris.reserve(512); + tribuf->loops.reserve(512); +} + +static uintptr_t tri_loopkey( + BMLoop *l, int mat_nr, int cd_fset, int cd_uvs[], int totuv, int cd_vert_boundary) +{ + uintptr_t key = ((uintptr_t)l->v) << 12ULL; int i = 0; - for (BMVert *v : node->bm_unique_verts) { - BKE_pbvh_bmesh_node_copy_original_co(log, node, v, i, use_original); - i++; - } - for (BMVert *v : node->bm_other_verts) { - BKE_pbvh_bmesh_node_copy_original_co(log, node, v, i, use_original); - i++; - } - /* Likely this is already dirty. */ - bm->elem_index_dirty |= BM_VERT; - /* Copy the triangles */ - i = 0; - for (BMFace *f : node->bm_faces) { - if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + key ^= (uintptr_t)BLI_hash_int(mat_nr + i++); + + if (cd_fset >= 0) { + // key ^= (uintptr_t)BLI_hash_int(BM_ELEM_CD_GET_INT(l->f, cd_fset)); + key ^= (uintptr_t)BLI_hash_int(BM_ELEM_CD_GET_INT(l->f, cd_fset) + i++); + } + + /* Split by sharp flags.*/ + bool sharp = BM_ELEM_CD_GET_INT(l->v, cd_vert_boundary) & SCULPT_BOUNDARY_SHARP_MARK; + if (sharp) { + key ^= (uintptr_t)l; + + return key; + } + + for (int i = 0; i < totuv; i++) { + float *luv = BM_ELEM_CD_PTR(l, cd_uvs[i]); + float snap = 4196.0f; + + uintptr_t x = (uintptr_t)(luv[0] * snap); + uintptr_t y = (uintptr_t)(luv[1] * snap); + + uintptr_t key2 = y * snap + x; + key ^= BLI_hash_int(key2 + i++); + } + + return key; +} + +/* In order to perform operations on the original node coordinates + * (currently just raycast), store the node's triangles and vertices. + * + * Skips triangles that are hidden. */ +bool BKE_pbvh_bmesh_check_tris(PBVH *pbvh, PBVHNode *node) +{ + BMesh *bm = pbvh->header.bm; + + if (!(node->flag & PBVH_UpdateTris) && node->tribuf) { + return false; + } + + node->flag |= PBVH_RebuildDrawBuffers | PBVH_UpdateDrawBuffers; + + int totuv = CustomData_number_of_layers(&bm->ldata, CD_PROP_FLOAT2); + int *cd_uvs = (int *)BLI_array_alloca(cd_uvs, totuv); + + for (int i = 0; i < totuv; i++) { + int idx = CustomData_get_layer_index_n(&bm->ldata, CD_PROP_FLOAT2, i); + cd_uvs[i] = bm->ldata.layers[idx].offset; + } + + int mat_map[MAXMAT]; + + for (int i = 0; i < MAXMAT; i++) { + mat_map[i] = -1; + } + + if (node->tribuf || node->tri_buffers) { + BKE_pbvh_bmesh_free_tris(pbvh, node); + } + + blender::Map vertmap; + + node->tribuf = MEM_new("node->tribuf"); + pbvh_init_tribuf(node, node->tribuf); + + Vector loops; + Vector loops_idx; + Vector *tribufs = MEM_new>("PBVHTriBuf tribufs"); + + node->flag &= ~PBVH_UpdateTris; + + const int edgeflag = BM_ELEM_TAG_ALT; + + float min[3], max[3]; + INIT_MINMAX(min, max); + + int ni = int(node - &pbvh->nodes[0]); + + auto add_tri_verts = [cd_uvs, totuv, pbvh, &min, &max, ni, &vertmap]( + PBVHTriBuf *tribuf, PBVHTri &tri, BMLoop *l, int mat_nr, int j) { + int tri_v; + + if ((l->f->head.hflag & BM_ELEM_SMOOTH)) { + /* This is called in threads, so updating boundary flags can be tricky. */ + if (BM_ELEM_CD_GET_INT(l->v, pbvh->cd_boundary_flag) & SCULPT_BOUNDARY_NEEDS_UPDATE) { + if (BM_ELEM_CD_GET_INT(l->v, pbvh->cd_vert_node_offset) == ni) { + pbvh_check_vert_boundary_bmesh(pbvh, l->v); + } + } + + void *loopkey = reinterpret_cast( + tri_loopkey(l, mat_nr, pbvh->cd_faceset_offset, cd_uvs, totuv, pbvh->cd_boundary_flag)); + + tri_v = vertmap.lookup_or_add(loopkey, tribuf->verts.size()); + } + else { /* Flat shaded faces. */ + tri_v = tribuf->verts.size(); + } + + /* Newly added to the set? */ + if (tri_v == tribuf->verts.size()) { + PBVHVertRef sv = {(intptr_t)l->v}; + minmax_v3v3_v3(min, max, l->v->co); + pbvh_tribuf_add_vert(tribuf, sv, l); + } + + tri.v[j] = (intptr_t)tri_v; + tri.l[j] = (intptr_t)l; + }; + + for (BMFace *f : *node->bm_faces) { + if (pbvh_poly_hidden(pbvh, f)) { continue; } - blender::bke::pbvh::bm_face_as_array_index_tri(f, node->bm_ortri[i]); - i++; + + /* Set edgeflag for building edge indices later. */ + BMLoop *l = f->l_first; + do { + l->e->head.hflag |= edgeflag; + } while ((l = l->next) != f->l_first); + + const int mat_nr = f->mat_nr; + + if (mat_map[mat_nr] == -1) { + PBVHTriBuf _tribuf = {}; + + mat_map[mat_nr] = tribufs->size(); + + pbvh_init_tribuf(node, &_tribuf); + _tribuf.mat_nr = mat_nr; + + tribufs->append(_tribuf); + } + + int tottri = (f->len - 2); + + loops.resize(f->len); + loops_idx.resize(tottri); + + BM_face_calc_tessellation(f, true, loops.data(), (uint(*)[3])loops_idx.data()); + + /* Build index buffers. */ + for (int i = 0; i < tottri; i++) { + PBVHTriBuf *mat_tribuf = &(*tribufs)[mat_map[mat_nr]]; + + node->tribuf->tris.resize(node->tribuf->tris.size() + 1); + mat_tribuf->tris.resize(mat_tribuf->tris.size() + 1); + + PBVHTri &tri = node->tribuf->tris.last(); + PBVHTri &mat_tri = mat_tribuf->tris.last(); + + tri.eflag = mat_tri.eflag = 0; + + for (int j = 0; j < 3; j++) { + BMLoop *l1 = loops[loops_idx[i][j]]; + + add_tri_verts(node->tribuf, tri, l1, mat_nr, j); + add_tri_verts(mat_tribuf, mat_tri, l1, mat_nr, j); + } + + for (int j = 0; j < 3; j++) { + BMLoop *l1 = loops[loops_idx[i][j]]; + BMLoop *l2 = loops[loops_idx[i][(j + 1) % 3]]; + BMEdge *e = nullptr; + + if ((e = BM_edge_exists(l1->v, l2->v))) { + tri.eflag |= 1 << j; + + if (e->head.hflag & edgeflag) { + e->head.hflag &= ~edgeflag; + pbvh_tribuf_add_edge(node->tribuf, tri.v[j], tri.v[(j + 1) % 3]); + pbvh_tribuf_add_edge(mat_tribuf, tri.v[j], tri.v[(j + 1) % 3]); + } + } + } + + copy_v3_v3(tri.no, f->no); + copy_v3_v3(mat_tri.no, f->no); + tri.f.i = (intptr_t)f; + mat_tri.f.i = (intptr_t)f; + } } - node->bm_tot_ortri = i; + /* + void *loopkey = reinterpret_cast( + tri_loopkey(l, mat_nr, pbvh->cd_faceset_offset, cd_uvs, totuv, + pbvh->cd_boundary_flag)); + */ + + bm->elem_index_dirty |= BM_VERT; + + node->tri_buffers = tribufs; + + if (node->tribuf->verts.size()) { + copy_v3_v3(node->tribuf->min, min); + copy_v3_v3(node->tribuf->max, max); + } + else { + zero_v3(node->tribuf->min); + zero_v3(node->tribuf->max); + } + + /* Compact memory, does a simple copy. */ + auto compact_vector = [&](auto &vector) { + if (vector.capacity() < size_t(double(vector.size()) * 1.25)) { + return; + } + + if (vector.size() == 0) { + vector.clear_and_shrink(); + return; + } + + using Type = std::remove_reference_t; +#if 1 + size_t count = vector.size(); + + Vector cpy; + cpy.reserve(count); + cpy.extend_unchecked(vector.data(), count); + + vector = std::move(cpy); + +#else + size_t count = vector.size(); + size_t size = sizeof(*vector.data()) * count; + + Type *mem = static_cast(MEM_mallocN(size, "temp")); + memcpy(static_cast(mem), static_cast(vector.data()), size); + + vector.clear_and_shrink(); + vector.reserve(count); + vector.extend_unchecked(mem, count); + + MEM_freeN(static_cast(mem)); +#endif + // + }; + + auto compact_tribuf = [&compact_vector](PBVHTriBuf *tribuf) { + compact_vector(tribuf->verts); + compact_vector(tribuf->edges); + compact_vector(tribuf->loops); + compact_vector(tribuf->tris); + }; + + compact_tribuf(node->tribuf); + + for (PBVHTriBuf &tribuf : *tribufs) { + compact_tribuf(&tribuf); + } + + return true; } -void BKE_pbvh_bmesh_after_stroke(PBVH *pbvh) +static int pbvh_count_subtree_verts(PBVH *pbvh, PBVHNode *n) { - const int totnode = pbvh->nodes.size(); - for (int i = 0; i < totnode; i++) { - PBVHNode *n = &pbvh->nodes[i]; - if (n->flag & PBVH_Leaf) { - /* Free orco/ortri data. */ - blender::bke::pbvh::pbvh_bmesh_node_drop_orig(n); + if (n->flag & PBVH_Leaf) { + n->subtree_tottri = n->bm_faces->size(); + return n->subtree_tottri; + } - /* Recursively split nodes that have gotten too many elements. */ - blender::bke::pbvh::pbvh_bmesh_node_limit_ensure(pbvh, i); + int ni = n->children_offset; + + int ret = pbvh_count_subtree_verts(pbvh, &pbvh->nodes[ni]); + ret += pbvh_count_subtree_verts(pbvh, &pbvh->nodes[ni + 1]); + + n->subtree_tottri = ret; + + return ret; +} + +void BKE_pbvh_bmesh_update_all_valence(PBVH *pbvh) +{ + BMIter iter; + BMVert *v; + + BM_ITER_MESH (v, &iter, pbvh->header.bm, BM_VERTS_OF_MESH) { + BKE_pbvh_bmesh_update_valence(pbvh, BKE_pbvh_make_vref((intptr_t)v)); + } +} + +bool BKE_pbvh_bmesh_mark_update_valence(PBVH *pbvh, PBVHVertRef vertex) +{ + BMVert *v = (BMVert *)vertex.i; + + bool ret = (*BM_ELEM_CD_PTR(v, pbvh->cd_flag)) & SCULPTFLAG_NEED_VALENCE; + dyntopo_add_flag(pbvh, v, SCULPTFLAG_NEED_VALENCE); + + return ret; +} + +bool BKE_pbvh_bmesh_check_valence(PBVH *pbvh, PBVHVertRef vertex) +{ + BMVert *v = (BMVert *)vertex.i; + + if (*BM_ELEM_CD_PTR(v, pbvh->cd_flag) & SCULPTFLAG_NEED_VALENCE) { + BKE_pbvh_bmesh_update_valence(pbvh, vertex); + return true; + } + + return false; +} + +void BKE_pbvh_bmesh_update_valence(PBVH *pbvh, PBVHVertRef vertex) +{ + BMVert *v = (BMVert *)vertex.i; + BMEdge *e; + + uint8_t *flag = BM_ELEM_CD_PTR(v, pbvh->cd_flag); + uint *valence = BM_ELEM_CD_PTR(v, pbvh->cd_valence); + + *flag &= ~SCULPTFLAG_NEED_VALENCE; + + if (!v->e) { + *valence = 0; + return; + } + + *valence = 0; + + e = v->e; + + if (!e) { + return; + } + + do { + (*valence)++; + + e = v == e->v1 ? e->v1_disk_link.next : e->v2_disk_link.next; + + if (!e) { + printf("bmesh error!\n"); + break; + } + } while (e != v->e); +} + +static void pbvh_bmesh_join_subnodes(PBVH *pbvh, PBVHNode *node, PBVHNode *parent) +{ + if (!(node->flag & PBVH_Leaf)) { + int ni = node->children_offset; + + if (ni > 0 && ni < pbvh->nodes.size() - 1) { + pbvh_bmesh_join_subnodes(pbvh, &pbvh->nodes[ni], parent); + pbvh_bmesh_join_subnodes(pbvh, &pbvh->nodes[ni + 1], parent); + } + else { + printf("node corruption: %d\n", ni); + return; + } + if (node != parent) { + node->flag |= PBVH_Delete; /* Mark for deletion. */ + } + + return; + } + + if (node != parent) { + node->flag |= PBVH_Delete; /* Mark for deletion. */ + } + + for (BMVert *v : *node->bm_unique_verts) { + parent->bm_unique_verts->add(v); + + int *flags = BM_ELEM_CD_PTR(v, pbvh->cd_boundary_flag); + *flags |= SCULPT_BOUNDARY_NEEDS_UPDATE; + + BM_ELEM_CD_SET_INT(v, pbvh->cd_vert_node_offset, DYNTOPO_NODE_NONE); + } + + for (BMFace *f : *node->bm_faces) { + parent->bm_faces->add(f); + BM_ELEM_CD_SET_INT(f, pbvh->cd_face_node_offset, DYNTOPO_NODE_NONE); + } +} + +static void BKE_pbvh_bmesh_correct_tree(PBVH *pbvh, PBVHNode *node, PBVHNode * /*parent*/) +{ + const int size_lower = pbvh->leaf_limit - (pbvh->leaf_limit >> 1); + + if (node->flag & PBVH_Leaf) { + return; + } + + if (node->subtree_tottri < size_lower && node != &pbvh->nodes[0]) { + node->bm_unique_verts = MEM_new>("bm_unique_verts"); + node->bm_other_verts = MEM_new>("bm_other_verts"); + node->bm_faces = MEM_new>("bm_faces"); + + pbvh_bmesh_join_subnodes(pbvh, &pbvh->nodes[node->children_offset], node); + pbvh_bmesh_join_subnodes(pbvh, &pbvh->nodes[node->children_offset + 1], node); + + node->children_offset = 0; + node->flag |= PBVH_Leaf | PBVH_UpdateRedraw | PBVH_UpdateBB | PBVH_UpdateDrawBuffers | + PBVH_RebuildDrawBuffers | PBVH_UpdateOriginalBB | PBVH_UpdateMask | + PBVH_UpdateVisibility | PBVH_UpdateColor | PBVH_UpdateNormals | PBVH_UpdateTris; + + DyntopoSet *other = MEM_new>("bm_other_verts"); + + node->children_offset = 0; + node->draw_batches = nullptr; + + /* Rebuild bm_other_verts. */ + for (BMFace *f : *node->bm_faces) { + BMLoop *l = f->l_first; + + if (BM_elem_is_free((BMElem *)f, BM_FACE)) { + printf("%s: corrupted face %p.\n", __func__, f); + node->bm_faces->remove(f); + continue; + } + + BM_ELEM_CD_SET_INT(f, pbvh->cd_face_node_offset, DYNTOPO_NODE_NONE); + + do { + if (!node->bm_unique_verts->contains(l->v)) { + other->add(l->v); + } + l = l->next; + } while (l != f->l_first); + } + + MEM_delete(node->bm_other_verts); + node->bm_other_verts = other; + + node->vb = negative_bounds(); + + for (BMVert *v : *node->bm_unique_verts) { + node->vb = bounds::expand(node->vb, float3(v->co)); + } + + for (BMVert *v : *node->bm_other_verts) { + node->vb = bounds::expand(node->vb, float3(v->co)); + } + + node->orig_vb = node->vb; + + return; + } + + int ni = node->children_offset; + + for (int i = 0; i < 2; i++, ni++) { + PBVHNode *child = &pbvh->nodes[ni]; + BKE_pbvh_bmesh_correct_tree(pbvh, child, node); + } +} + +/* Deletes PBVH_Delete marked nodes. */ +static void pbvh_bmesh_compact_tree(PBVH *bvh) +{ + // compact nodes + int totnode = 0; + for (int i = 0; i < bvh->nodes.size(); i++) { + PBVHNode *n = &bvh->nodes[i]; + + if (!(n->flag & PBVH_Delete)) { + if (!(n->flag & PBVH_Leaf)) { + PBVHNode *n1 = &bvh->nodes[n->children_offset]; + PBVHNode *n2 = &bvh->nodes[n->children_offset + 1]; + + if ((n1->flag & PBVH_Delete) != (n2->flag & PBVH_Delete)) { + printf("un-deleting an empty node\n"); + PBVHNode *n3 = n1->flag & PBVH_Delete ? n1 : n2; + + n3->flag = PBVH_Leaf | PBVH_UpdateTris; + n3->bm_unique_verts = MEM_new>("bm_unique_verts"); + n3->bm_other_verts = MEM_new>("bm_other_verts"); + n3->bm_faces = MEM_new>("bm_faces"); + n3->tribuf = nullptr; + n3->draw_batches = nullptr; + } + else if ((n1->flag & PBVH_Delete) && (n2->flag & PBVH_Delete)) { + n->children_offset = 0; + n->flag |= PBVH_Leaf | PBVH_UpdateTris; + + if (!n->bm_unique_verts) { + // should not happen + n->bm_unique_verts = MEM_new>("bm_unique_verts"); + n->bm_other_verts = MEM_new>("bm_other_verts"); + n->bm_faces = MEM_new>("bm_faces"); + n->tribuf = nullptr; + n->draw_batches = nullptr; + } + } + } + + totnode++; + } + } + + int *map = MEM_cnew_array(bvh->nodes.size(), "bmesh map temp"); + + // build idx map for child offsets + int j = 0; + for (int i = 0; i < bvh->nodes.size(); i++) { + PBVHNode *n = &bvh->nodes[i]; + + if (!(n->flag & PBVH_Delete)) { + map[i] = j++; + } + else if (1) { + if (n->layer_disp) { + MEM_freeN(n->layer_disp); + n->layer_disp = nullptr; + } + + blender::bke::pbvh::free_draw_buffers(bvh, n); + + if (n->tribuf || n->tri_buffers) { + BKE_pbvh_bmesh_free_tris(bvh, n); + } + + if (n->bm_unique_verts) { + MEM_delete(n->bm_unique_verts); + n->bm_unique_verts = nullptr; + } + + if (n->bm_other_verts) { + MEM_delete(n->bm_other_verts); + n->bm_other_verts = nullptr; + } + + if (n->bm_faces) { + MEM_delete(n->bm_faces); + n->bm_faces = nullptr; + } + +#ifdef PROXY_ADVANCED + BKE_pbvh_free_proxyarray(bvh, n); +#endif + } + } + + // compact node array + j = 0; + for (int i = 0; i < bvh->nodes.size(); i++) { + if (!(bvh->nodes[i].flag & PBVH_Delete)) { + if (bvh->nodes[i].children_offset >= bvh->nodes.size() - 1) { + printf("error %i %i\n", i, bvh->nodes[i].children_offset); + continue; + } + + int i1 = map[bvh->nodes[i].children_offset]; + int i2 = map[bvh->nodes[i].children_offset + 1]; + + if (bvh->nodes[i].children_offset >= bvh->nodes.size()) { + printf("bad child node reference %d->%d, totnode: %d\n", + i, + bvh->nodes[i].children_offset, + int(bvh->nodes.size())); + continue; + } + + if (bvh->nodes[i].children_offset && i2 != i1 + 1) { + printf(" pbvh corruption during node join %d %d\n", i1, i2); + } + + bvh->nodes[j] = bvh->nodes[i]; + bvh->nodes[j].children_offset = i1; + + j++; + } + } + + if (j != totnode) { + printf("pbvh error: %s", __func__); + } + + bvh->nodes.resize(j); + + // set vert/face node indices again + for (int i = 0; i < bvh->nodes.size(); i++) { + PBVHNode *n = &bvh->nodes[i]; + + if (!(n->flag & PBVH_Leaf)) { + continue; + } + + if (!n->bm_unique_verts) { + printf("%s: pbvh error\n", __func__); + + n->bm_unique_verts = MEM_new>("bm_unique_verts"); + n->bm_other_verts = MEM_new>("bm_other_verts"); + n->bm_faces = MEM_new>("bm_faces"); + } + + for (BMVert *v : *n->bm_unique_verts) { + BM_ELEM_CD_SET_INT(v, bvh->cd_vert_node_offset, i); + } + + for (BMFace *f : *n->bm_faces) { + BM_ELEM_CD_SET_INT(f, bvh->cd_face_node_offset, i); + } + } + + Vector scratch; + + for (int i = 0; i < bvh->nodes.size(); i++) { + PBVHNode *n = &bvh->nodes[i]; + + if (!(n->flag & PBVH_Leaf)) { + continue; + } + + scratch.clear(); + + for (BMVert *v : *n->bm_other_verts) { + int ni = BM_ELEM_CD_GET_INT(v, bvh->cd_vert_node_offset); + if (ni == DYNTOPO_NODE_NONE) { + scratch.append(v); + } + } + + int slen = scratch.size(); + for (int j = 0; j < slen; j++) { + BMVert *v = scratch[j]; + + n->bm_other_verts->remove(v); + n->bm_unique_verts->add(v); + BM_ELEM_CD_SET_INT(v, bvh->cd_vert_node_offset, i); + } + } + + MEM_freeN(map); +} + +namespace blender::bke::pbvh { +void split_bmesh_nodes(PBVH *pbvh) +{ + for (int i : pbvh->nodes.index_range()) { + PBVHNode *n = &pbvh->nodes[i]; + + if (n->flag & PBVH_Leaf) { + /* Recursively split nodes that have gotten too many + * elements */ + pbvh_bmesh_node_limit_ensure(pbvh, i); + } + } +} +} // namespace blender::bke::pbvh + +/* Prunes leaf nodes that are too small or degenerate. */ +static void pbvh_bmesh_balance_tree(PBVH *pbvh) +{ + float *overlaps = MEM_cnew_array(pbvh->nodes.size(), "overlaps"); + PBVHNode **parentmap = MEM_cnew_array(pbvh->nodes.size(), "parentmap"); + int *depthmap = MEM_cnew_array(pbvh->nodes.size(), "depthmap"); + + Vector stack; + Vector faces; + Vector substack; + + for (int i = 0; i < pbvh->nodes.size(); i++) { + PBVHNode *node = &pbvh->nodes[i]; + + if ((node->flag & PBVH_Leaf) || node->children_offset == 0) { + continue; + } + + if (node->children_offset < pbvh->nodes.size()) { + parentmap[node->children_offset] = node; + } + + if (node->children_offset + 1 < pbvh->nodes.size()) { + parentmap[node->children_offset + 1] = node; + } + } + + const int cd_vert_node = pbvh->cd_vert_node_offset; + const int cd_face_node = pbvh->cd_face_node_offset; + + bool modified = false; + + stack.append(&pbvh->nodes[0]); + + while (stack.size() > 0) { + PBVHNode *node = stack.pop_last(); + Bounds clip; + + if (!(node->flag & PBVH_Leaf) && node->children_offset > 0) { + PBVHNode *child1 = &pbvh->nodes[node->children_offset]; + PBVHNode *child2 = &pbvh->nodes[node->children_offset + 1]; + + float volume = bounds::volume(child1->vb) + bounds::volume(child2->vb); + + /* dissolve nodes whose children overlap by more then a percentage + of the total volume. we use a simple huerstic to calculate the + cutoff threshold.*/ + + clip = bounds::intersect(child1->vb, child2->vb); + float overlap = bounds::volume(clip); + float factor; + + factor = 0.2f; + + bool bad = overlap > volume * factor; + + bad |= child1->bm_faces && !child1->bm_faces->size(); + bad |= child2->bm_faces && !child2->bm_faces->size(); + + if (bad) { + modified = true; + + substack.clear(); + substack.append(child1); + substack.append(child2); + + while (substack.size() > 0) { + PBVHNode *node2 = substack.pop_last(); + + node2->flag |= PBVH_Delete; + + if (node2->flag & PBVH_Leaf) { + for (BMFace *f : *node2->bm_faces) { + if (BM_ELEM_CD_GET_INT(f, cd_face_node) == -1) { + // eek! + continue; + } + + BM_ELEM_CD_SET_INT(f, cd_face_node, DYNTOPO_NODE_NONE); + faces.append(f); + } + + for (BMVert *v : *node2->bm_unique_verts) { + int *flags = BM_ELEM_CD_PTR(v, pbvh->cd_boundary_flag); + *flags |= SCULPT_BOUNDARY_NEEDS_UPDATE; + + BM_ELEM_CD_SET_INT(v, cd_vert_node, DYNTOPO_NODE_NONE); + } + } + else if (node2->children_offset > 0 && node2->children_offset < pbvh->nodes.size()) { + substack.append(&pbvh->nodes[node2->children_offset]); + + if (node2->children_offset + 1 < pbvh->nodes.size()) { + substack.append(&pbvh->nodes[node2->children_offset + 1]); + } + } + } + } + + if (node->children_offset < pbvh->nodes.size()) { + stack.append(child1); + } + + if (node->children_offset + 1 < pbvh->nodes.size()) { + stack.append(child2); + } + } + } + + if (modified) { + pbvh_bmesh_compact_tree(pbvh); + + printf("joined nodes; %d faces\n", (int)faces.size()); + + for (int i : IndexRange(faces.size())) { + if (BM_elem_is_free((BMElem *)faces[i], BM_FACE)) { + printf("corrupted face in pbvh tree; faces[i]: %p\n", faces[i]); + continue; + } + + if (BM_ELEM_CD_GET_INT(faces[i], cd_face_node) != DYNTOPO_NODE_NONE) { + // printf("duplicate faces in pbvh_bmesh_balance_tree!\n"); + continue; + } + + bke_pbvh_insert_face(pbvh, faces[i]); + } + } + + MEM_SAFE_FREE(parentmap); + MEM_SAFE_FREE(overlaps); + MEM_SAFE_FREE(depthmap); +} + +/* Fix any orphaned empty leaves that survived other stages of culling.*/ +static void pbvh_fix_orphan_leaves(PBVH *pbvh) +{ + for (int i = 0; i < pbvh->nodes.size(); i++) { + PBVHNode *node = &pbvh->nodes[i]; + + if (!(node->flag & PBVH_Leaf) || (node->flag & PBVH_Delete) || node->bm_faces->size() != 0) { + continue; + } + + /* Find parent node. */ + PBVHNode *parent = nullptr; + + for (int j = 0; j < pbvh->nodes.size(); j++) { + if (pbvh->nodes[j].children_offset == i || pbvh->nodes[j].children_offset + 1 == i) { + parent = &pbvh->nodes[j]; + break; + } + } + + if (!parent) { + printf("%s: node corruption, could not find parent node!\n", __func__); + continue; + } + + PBVHNode *other = &pbvh->nodes[(parent->children_offset == i ? i + 1 : i - 1)]; + + if (other->flag & PBVH_Delete) { + printf("%s: error!\n", __func__); + continue; + } + + while (!(other->flag & PBVH_Leaf)) { + PBVHNode *a = &pbvh->nodes[other->children_offset]; + PBVHNode *b = &pbvh->nodes[other->children_offset + 1]; + + if (!(a->flag & PBVH_Delete) && (a->flag & PBVH_Leaf) && a->bm_faces->size() > 1) { + other = a; + } + else if (!(b->flag & PBVH_Delete) && (b->flag & PBVH_Leaf) && b->bm_faces->size() > 1) { + other = b; + } + else { + other = nullptr; + break; + } + } + + if (other == nullptr || other->bm_faces->size() < 1) { + printf("%s: other was nullptr\n", __func__); + continue; + } + + /* Steal a single face from other */ + PBVHNodeFlags updateflag = PBVH_UpdateOtherVerts | PBVH_UpdateBB | PBVH_UpdateOriginalBB | + PBVH_UpdateTris | PBVH_UpdateTriAreas | PBVH_RebuildDrawBuffers | + PBVH_RebuildNodeVerts | PBVH_RebuildPixels | PBVH_UpdateNormals | + PBVH_UpdateCurvatureDir | PBVH_UpdateRedraw | PBVH_UpdateVisibility; + + for (BMFace *f : *other->bm_faces) { + other->bm_faces->remove(f); + node->bm_faces->add(f); + BM_ELEM_CD_SET_INT(f, pbvh->cd_face_node_offset, i); + + BMVert *v = f->l_first->v; + int node_i = BM_ELEM_CD_GET_INT(v, pbvh->cd_vert_node_offset); + if (node_i != DYNTOPO_NODE_NONE) { + PBVHNode *node2 = &pbvh->nodes[node_i]; + if (node2->bm_unique_verts->contains(v)) { + node2->bm_unique_verts->remove(v); + } + } + + BM_ELEM_CD_SET_INT(v, pbvh->cd_vert_node_offset, i); + node->bm_unique_verts->add(v); + + node->flag |= updateflag; + other->flag |= updateflag; + + printf("%s: Patched empty leaf node.\n", __func__); + break; } } } -void BKE_pbvh_bmesh_detail_size_set(PBVH *pbvh, float detail_size) +static void pbvh_bmesh_join_nodes(PBVH *pbvh) { - pbvh->bm_max_edge_len = detail_size; - pbvh->bm_min_edge_len = pbvh->bm_max_edge_len * 0.4f; + if (pbvh->nodes.size() < 2) { + return; + } + + pbvh_count_subtree_verts(pbvh, &pbvh->nodes[0]); + BKE_pbvh_bmesh_correct_tree(pbvh, &pbvh->nodes[0], nullptr); + pbvh_fix_orphan_leaves(pbvh); + + /* Compact nodes. */ + int totnode = 0; + int *map = MEM_cnew_array(pbvh->nodes.size(), "bmesh map temp"); + + for (int i = 0; i < pbvh->nodes.size(); i++) { + for (int j = 0; j < pbvh->nodes.size(); j++) { + if (i == j || !pbvh->nodes[i].draw_batches) { + continue; + } + + if (pbvh->nodes[i].draw_batches == pbvh->nodes[j].draw_batches) { + printf("%s: error %d %d\n", __func__, i, j); + + pbvh->nodes[j].draw_batches = nullptr; + } + } + } + + /* Build index map for child offsets. */ + int j = 0; + for (int i = 0; i < pbvh->nodes.size(); i++) { + PBVHNode *n = &pbvh->nodes[i]; + + if (!(n->flag & PBVH_Delete)) { + map[i] = j++; + totnode++; + } + else { + if (n->layer_disp) { + MEM_freeN(n->layer_disp); + n->layer_disp = nullptr; + } + + blender::bke::pbvh::free_draw_buffers(pbvh, n); + + if (n->tribuf || n->tri_buffers) { + BKE_pbvh_bmesh_free_tris(pbvh, n); + } + + if (n->bm_unique_verts) { + MEM_delete(n->bm_unique_verts); + n->bm_unique_verts = nullptr; + } + + if (n->bm_other_verts) { + MEM_delete(n->bm_other_verts); + n->bm_other_verts = nullptr; + } + + if (n->bm_faces) { + MEM_delete(n->bm_faces); + n->bm_faces = nullptr; + } + +#ifdef PROXY_ADVANCED + BKE_pbvh_free_proxyarray(pbvh, n); +#endif + } + } + + // compact node array + j = 0; + for (int i = 0; i < pbvh->nodes.size(); i++) { + if (!(pbvh->nodes[i].flag & PBVH_Delete)) { + if (pbvh->nodes[i].children_offset >= pbvh->nodes.size() - 1) { + printf("%s: error %i %i\n", __func__, i, pbvh->nodes[i].children_offset); + continue; + } + + int i1 = map[pbvh->nodes[i].children_offset]; + int i2 = map[pbvh->nodes[i].children_offset + 1]; + + if (pbvh->nodes[i].children_offset >= pbvh->nodes.size()) { + printf("%s: Bad child node reference %d->%d, totnode: %d\n", + __func__, + i, + pbvh->nodes[i].children_offset, + int(pbvh->nodes.size())); + continue; + } + + if (pbvh->nodes[i].children_offset && i2 != i1 + 1) { + printf(" pbvh corruption during node join %d %d\n", i1, i2); + } + + pbvh->nodes[j] = pbvh->nodes[i]; + pbvh->nodes[j].children_offset = i1; + + j++; + } + } + + if (j != totnode) { + printf("%s: pbvh error.\n", __func__); + } + + pbvh->nodes.resize(j); + + // set vert/face node indices again + for (int i = 0; i < pbvh->nodes.size(); i++) { + PBVHNode *n = &pbvh->nodes[i]; + + if (!(n->flag & PBVH_Leaf)) { + continue; + } + + if (!n->bm_unique_verts) { + printf("%s: pbvh error.\n", __func__); + n->bm_unique_verts = MEM_new>("bm_unique_verts"); + n->bm_other_verts = MEM_new>("bm_other_verts"); + n->bm_faces = MEM_new>("bm_faces"); + } + + for (BMVert *v : *n->bm_unique_verts) { + BM_ELEM_CD_SET_INT(v, pbvh->cd_vert_node_offset, i); + } + + for (BMFace *f : *n->bm_faces) { + BM_ELEM_CD_SET_INT(f, pbvh->cd_face_node_offset, i); + } + } + + Vector scratch; + + for (int i = 0; i < pbvh->nodes.size(); i++) { + PBVHNode *n = &pbvh->nodes[i]; + + if (!(n->flag & PBVH_Leaf)) { + continue; + } + + scratch.clear(); + + for (BMVert *v : *n->bm_other_verts) { + int ni = BM_ELEM_CD_GET_INT(v, pbvh->cd_vert_node_offset); + if (ni == DYNTOPO_NODE_NONE) { + scratch.append(v); + } + } + + for (int j : IndexRange(scratch.size())) { + BMVert *v = scratch[j]; + + n->bm_other_verts->remove(v); + n->bm_unique_verts->add(v); + BM_ELEM_CD_SET_INT(v, pbvh->cd_vert_node_offset, i); + } + } + + MEM_freeN(map); } +namespace blender::bke::dyntopo { +void after_stroke(PBVH *pbvh, bool force_balance) +{ + blender::bke::pbvh::update_bounds(*pbvh, + (PBVH_UpdateBB | PBVH_UpdateOriginalBB | PBVH_UpdateRedraw)); + + pbvh_bmesh_check_nodes(pbvh); + pbvh_bmesh_join_nodes(pbvh); + pbvh_bmesh_check_nodes(pbvh); + + blender::bke::pbvh::update_bounds(*pbvh, + (PBVH_UpdateBB | PBVH_UpdateOriginalBB | PBVH_UpdateRedraw)); + + if (force_balance || pbvh->balance_counter++ == 10) { + pbvh_bmesh_balance_tree(pbvh); + pbvh_bmesh_check_nodes(pbvh); + pbvh->balance_counter = 0; + + int totnode = pbvh->nodes.size(); + + for (int i = 0; i < totnode; i++) { + PBVHNode *n = &pbvh->nodes[i]; + + if (totnode != pbvh->nodes.size()) { +#ifdef PROXY_ADVANCED + BKE_pbvh_free_proxyarray(pbvh, n); +#endif + } + + if (n->flag & PBVH_Leaf) { + /* Recursively split nodes that have gotten too many + * elements */ + pbvh_bmesh_node_limit_ensure(pbvh, i); + } + } + } + + pbvh_print_mem_size(pbvh); +} +} // namespace blender::bke::dyntopo + void BKE_pbvh_node_mark_topology_update(PBVHNode *node) { node->flag |= PBVH_UpdateTopology; } -const blender::Set &BKE_pbvh_bmesh_node_unique_verts(PBVHNode *node) +DyntopoSet &BKE_pbvh_bmesh_node_unique_verts(PBVHNode *node) { - return node->bm_unique_verts; + return *node->bm_unique_verts; } -const blender::Set &BKE_pbvh_bmesh_node_other_verts(PBVHNode *node) +DyntopoSet &BKE_pbvh_bmesh_node_other_verts(PBVHNode *node) { - return node->bm_other_verts; + pbvh_bmesh_check_other_verts(node); + return *node->bm_other_verts; } -const blender::Set &BKE_pbvh_bmesh_node_faces(PBVHNode *node) +DyntopoSet &BKE_pbvh_bmesh_node_faces(PBVHNode *node) { - return node->bm_faces; + return *node->bm_faces; } /****************************** Debugging *****************************/ -#if 0 +namespace blender::bke::pbvh { -static void pbvh_bmesh_print(PBVH *pbvh) +void update_offsets(PBVH *pbvh, + const int cd_vert_node_offset, + const int cd_face_node_offset, + const int cd_face_areas, + const int cd_boundary_flag, + const int cd_edge_boundary, + const int cd_flag, + const int cd_valence, + const int cd_origco, + const int cd_origno, + const int cd_curvature_dir) { - fprintf(stderr, "\npbvh=%p\n", pbvh); - fprintf(stderr, "bm_face_to_node:\n"); + pbvh->cd_face_node_offset = cd_face_node_offset; + pbvh->cd_vert_node_offset = cd_vert_node_offset; + pbvh->cd_face_area = cd_face_areas; + pbvh->cd_vert_mask_offset = CustomData_get_offset_named( + &pbvh->header.bm->vdata, CD_PROP_FLOAT, ".sculpt_mask"); + pbvh->cd_faceset_offset = CustomData_get_offset_named( + &pbvh->header.bm->pdata, CD_PROP_INT32, ".sculpt_face_set"); - BMIter iter; - BMFace *f; - BM_ITER_MESH (f, &iter, pbvh->header.bm, BM_FACES_OF_MESH) { - fprintf( - stderr, " %d -> %d\n", BM_elem_index_get(f), pbvh_bmesh_node_index_from_face(pbvh, f)); + CustomData *ldata = &pbvh->header.bm->ldata; + pbvh->totuv = 0; + for (int i : IndexRange(ldata->totlayer)) { + CustomDataLayer &layer = ldata->layers[i]; + if (layer.type == CD_PROP_FLOAT2 && !(layer.flag & CD_FLAG_TEMPORARY)) { + pbvh->totuv++; + } } - fprintf(stderr, "bm_vert_to_node:\n"); - BMVert *v; - BM_ITER_MESH (v, &iter, pbvh->header.bm, BM_FACES_OF_MESH) { - fprintf( - stderr, " %d -> %d\n", BM_elem_index_get(v), pbvh_bmesh_node_index_from_vert(pbvh, v)); + pbvh->cd_boundary_flag = cd_boundary_flag; + pbvh->cd_edge_boundary = cd_edge_boundary; + + pbvh->cd_curvature_dir = cd_curvature_dir; + + if (pbvh->bm_idmap) { + BM_idmap_check_attributes(pbvh->bm_idmap); } - for (int n = 0; n < pbvh->totnode; n++) { - PBVHNode *node = &pbvh->nodes[n]; - if (!(node->flag & PBVH_Leaf)) { + pbvh->cd_flag = cd_flag; + pbvh->cd_valence = cd_valence; + pbvh->cd_origco = cd_origco; + pbvh->cd_origno = cd_origno; +} + +float bmesh_detail_size_avg_get(PBVH *pbvh) +{ + return (pbvh->bm_max_edge_len + pbvh->bm_min_edge_len) * 0.5f; +} + +void update_sharp_vertex_bmesh(BMVert *v, int cd_boundary_flag, const float sharp_angle_limit) +{ + int flag = BM_ELEM_CD_GET_INT(v, cd_boundary_flag); + flag &= ~(SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE | SCULPT_BOUNDARY_SHARP_ANGLE | + SCULPT_CORNER_SHARP_ANGLE); + + if (!v->e) { + BM_ELEM_CD_SET_INT(v, cd_boundary_flag, flag); + return; + } + + int sharp_num = 0; + + BMEdge *e = v->e; + do { + if (!e->l || e->l == e->l->radial_next) { continue; } - GSetIterator gs_iter; - fprintf(stderr, "node %d\n faces:\n", n); - GSET_ITER (gs_iter, node->bm_faces) - fprintf(stderr, " %d\n", BM_elem_index_get((BMFace *)BLI_gsetIterator_getKey(&gs_iter))); - fprintf(stderr, " unique verts:\n"); - GSET_ITER (gs_iter, node->bm_unique_verts) - fprintf(stderr, " %d\n", BM_elem_index_get((BMVert *)BLI_gsetIterator_getKey(&gs_iter))); - fprintf(stderr, " other verts:\n"); - GSET_ITER (gs_iter, node->bm_other_verts) - fprintf(stderr, " %d\n", BM_elem_index_get((BMVert *)BLI_gsetIterator_getKey(&gs_iter))); + if (test_sharp_faces_bmesh(e->l->f, e->l->radial_next->f, sharp_angle_limit)) { + flag |= SCULPT_BOUNDARY_SHARP_ANGLE; + sharp_num++; + } + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + if (sharp_num > 2) { + flag |= SCULPT_CORNER_SHARP_ANGLE; } + + BM_ELEM_CD_SET_INT(v, cd_boundary_flag, flag); } - -static void print_flag_factors(int flag) -{ - printf("flag=0x%x:\n", flag); - for (int i = 0; i < 32; i++) { - if (flag & (1 << i)) { - printf(" %d (1 << %d)\n", 1 << i, i); - } - } -} -#endif - -#ifdef USE_VERIFY - -static void pbvh_bmesh_verify(PBVH *pbvh) -{ - /* Build list of faces & verts to lookup. */ - GSet *faces_all = BLI_gset_ptr_new_ex(__func__, pbvh->header.bm->totface); - BMIter iter; - - { - BMFace *f; - BM_ITER_MESH (f, &iter, pbvh->header.bm, BM_FACES_OF_MESH) { - BLI_assert(BM_ELEM_CD_GET_INT(f, pbvh->cd_face_node_offset) != DYNTOPO_NODE_NONE); - BLI_gset_insert(faces_all, f); - } - } - - GSet *verts_all = BLI_gset_ptr_new_ex(__func__, pbvh->header.bm->totvert); - { - BMVert *v; - BM_ITER_MESH (v, &iter, pbvh->header.bm, BM_VERTS_OF_MESH) { - if (BM_ELEM_CD_GET_INT(v, pbvh->cd_vert_node_offset) != DYNTOPO_NODE_NONE) { - BLI_gset_insert(verts_all, v); - } - } - } - - /* Check vert/face counts. */ - { - int totface = 0, totvert = 0; - for (int i = 0; i < pbvh->totnode; i++) { - PBVHNode *n = &pbvh->nodes[i]; - totface += n->bm_faces.is_empty() ? n->bm_faces.size() : 0; - totvert += n->bm_unique_verts ? n->bm_unique_verts.size() : 0; - } - - BLI_assert(totface == BLI_gset_len(faces_all)); - BLI_assert(totvert == BLI_gset_len(verts_all)); - } - - { - BMFace *f; - BM_ITER_MESH (f, &iter, pbvh->header.bm, BM_FACES_OF_MESH) { - BMIter bm_iter; - BMVert *v; - PBVHNode *n = pbvh_bmesh_node_lookup(pbvh, f); - - /* Check that the face's node is a leaf. */ - BLI_assert(n->flag & PBVH_Leaf); - - /* Check that the face's node knows it owns the face. */ - BLI_assert(n->bm_faces.contains(f)); - - /* Check the face's vertices... */ - BM_ITER_ELEM (v, &bm_iter, f, BM_VERTS_OF_FACE) { - PBVHNode *nv; - - /* Check that the vertex is in the node. */ - BLI_assert(BLI_gset_haskey(n->bm_unique_verts, v) ^ BLI_gset_haskey(n->bm_other_verts, v)); - - /* Check that the vertex has a node owner. */ - nv = pbvh_bmesh_node_lookup(pbvh, v); - - /* Check that the vertex's node knows it owns the vert. */ - BLI_assert(BLI_gset_haskey(nv->bm_unique_verts, v)); - - /* Check that the vertex isn't duplicated as an 'other' vert. */ - BLI_assert(!BLI_gset_haskey(nv->bm_other_verts, v)); - } - } - } - - /* Check verts */ - { - BMVert *v; - BM_ITER_MESH (v, &iter, pbvh->header.bm, BM_VERTS_OF_MESH) { - /* Vertex isn't tracked. */ - if (BM_ELEM_CD_GET_INT(v, pbvh->cd_vert_node_offset) == DYNTOPO_NODE_NONE) { - continue; - } - - PBVHNode *n = pbvh_bmesh_node_lookup(pbvh, v); - - /* Check that the vert's node is a leaf. */ - BLI_assert(n->flag & PBVH_Leaf); - - /* Check that the vert's node knows it owns the vert. */ - BLI_assert(BLI_gset_haskey(n->bm_unique_verts, v)); - - /* Check that the vertex isn't duplicated as an 'other' vert. */ - BLI_assert(!BLI_gset_haskey(n->bm_other_verts, v)); - - /* Check that the vert's node also contains one of the vert's adjacent faces. */ - bool found = false; - BMIter bm_iter; - BMFace *f = nullptr; - BM_ITER_ELEM (f, &bm_iter, v, BM_FACES_OF_VERT) { - if (pbvh_bmesh_node_lookup(pbvh, f) == n) { - found = true; - break; - } - } - BLI_assert(found || f == nullptr); - -# if 1 - /* total freak stuff, check if node exists somewhere else */ - /* Slow */ - for (int i = 0; i < pbvh->totnode; i++) { - PBVHNode *n_other = &pbvh->nodes[i]; - if ((n != n_other) && (n_other->bm_unique_verts)) { - BLI_assert(!BLI_gset_haskey(n_other->bm_unique_verts, v)); - } - } -# endif - } - } - -# if 0 - /* check that every vert belongs somewhere */ - /* Slow */ - BM_ITER_MESH (vi, &iter, pbvh->header.bm, BM_VERTS_OF_MESH) { - bool has_unique = false; - for (int i = 0; i < pbvh->totnode; i++) { - PBVHNode *n = &pbvh->nodes[i]; - if ((n->bm_unique_verts != nullptr) && BLI_gset_haskey(n->bm_unique_verts, vi)) { - has_unique = true; - } - } - BLI_assert(has_unique); - vert_count++; - } - - /* If totvert differs from number of verts inside the hash. hash-totvert is checked above. */ - BLI_assert(vert_count == pbvh->header.bm->totvert); -# endif - - /* Check that node elements are recorded in the top level */ - for (int i = 0; i < pbvh->totnode; i++) { - PBVHNode *n = &pbvh->nodes[i]; - if (n->flag & PBVH_Leaf) { - GSetIterator gs_iter; - - for (BMFace *f : n->bm_faces) { - PBVHNode *n_other = pbvh_bmesh_node_lookup(pbvh, f); - BLI_assert(n == n_other); - BLI_assert(BLI_gset_haskey(faces_all, f)); - } - - GSET_ITER (gs_iter, n->bm_unique_verts) { - BMVert *v = BLI_gsetIterator_getKey(&gs_iter); - PBVHNode *n_other = pbvh_bmesh_node_lookup(pbvh, v); - BLI_assert(!BLI_gset_haskey(n->bm_other_verts, v)); - BLI_assert(n == n_other); - BLI_assert(BLI_gset_haskey(verts_all, v)); - } - - GSET_ITER (gs_iter, n->bm_other_verts) { - BMVert *v = BLI_gsetIterator_getKey(&gs_iter); - /* this happens sometimes and seems harmless */ - // BLI_assert(!BM_vert_face_check(v)); - BLI_assert(BLI_gset_haskey(verts_all, v)); - } - } - } - - BLI_gset_free(faces_all, nullptr); - BLI_gset_free(verts_all, nullptr); -} - -#endif +} // namespace blender::bke::pbvh diff --git a/source/blender/blenkernel/intern/pbvh_colors.cc b/source/blender/blenkernel/intern/pbvh_colors.cc index c508c3716e0..b45c45fcb33 100644 --- a/source/blender/blenkernel/intern/pbvh_colors.cc +++ b/source/blender/blenkernel/intern/pbvh_colors.cc @@ -84,11 +84,58 @@ template<> void from_float(const float src[4], MPropCol &dst) copy_v4_v4(dst.color, src); } +template +static void pbvh_vertex_color_get_bmesh(const PBVH &pbvh, PBVHVertRef vertex, float r_color[4]) +{ + BMVert *v = reinterpret_cast(vertex.i); + int cd_color = pbvh.color_layer->offset; + + if (pbvh.color_domain == AttrDomain::Corner) { + zero_v4(r_color); + + if (!v->e) { + return; + } + + BMEdge *e = v->e; + int count = 0; + + do { + BMLoop *l = e->l; + if (l) { + do { + BMLoop *l_color = l->v != v ? l->next : l; + T *color = BM_ELEM_CD_PTR(l_color, cd_color); + + float temp[4]; + to_float(*color, temp); + add_v4_v4(r_color, temp); + count++; + } while ((l = l->radial_next) != e->l); + } + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + if (count) { + mul_v4_fl(r_color, 1.0f / float(count)); + } + + return; + } + + T *color = BM_ELEM_CD_PTR(v, cd_color); + to_float(*color, r_color); +} + template static void pbvh_vertex_color_get(const PBVH &pbvh, PBVHVertRef vertex, float r_color[4]) { int index = vertex.i; + if (pbvh.header.bm) { + pbvh_vertex_color_get_bmesh(pbvh, vertex, r_color); + return; + } + if (pbvh.color_domain == AttrDomain::Corner) { int count = 0; zero_v4(r_color); @@ -117,11 +164,44 @@ static void pbvh_vertex_color_get(const PBVH &pbvh, PBVHVertRef vertex, float r_ } } +template +static void pbvh_vertex_color_set_bmesh(const PBVH &pbvh, PBVHVertRef vertex, const float color[4]) +{ + BMVert *v = reinterpret_cast(vertex.i); + int cd_color = pbvh.color_layer->offset; + + if (pbvh.color_domain == AttrDomain::Corner) { + if (!v->e) { + return; + } + + BMEdge *e = v->e; + do { + BMLoop *l = e->l; + if (l) { + do { + BMLoop *l_color = l->v != v ? l->next : l; + from_float(color, *BM_ELEM_CD_PTR(l_color, cd_color)); + } while ((l = l->radial_next) != e->l); + } + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + return; + } + + from_float(color, *BM_ELEM_CD_PTR(v, cd_color)); +} + template static void pbvh_vertex_color_set(PBVH &pbvh, PBVHVertRef vertex, const float color[4]) { int index = vertex.i; + if (pbvh.header.bm) { + pbvh_vertex_color_set_bmesh(pbvh, vertex, color); + return; + } + if (pbvh.color_domain == AttrDomain::Corner) { for (const int i_face : pbvh.vert_to_face_map[index]) { const IndexRange face = pbvh.faces[i_face]; diff --git a/source/blender/blenkernel/intern/pbvh_intern.hh b/source/blender/blenkernel/intern/pbvh_intern.hh index 560ae87a467..bdd1e69e8da 100644 --- a/source/blender/blenkernel/intern/pbvh_intern.hh +++ b/source/blender/blenkernel/intern/pbvh_intern.hh @@ -5,42 +5,67 @@ #pragma once #include "BLI_array.hh" +#include "BLI_bit_span.hh" #include "BLI_bounds_types.hh" #include "BLI_math_vector_types.hh" +#include "BLI_offset_indices.hh" #include "BLI_set.hh" #include "BLI_span.hh" #include "BLI_vector.hh" +#include "BKE_paint.hh" /* for SCULPT_BOUNDARY_NEEDS_UPDATE */ +#include "BKE_pbvh_api.hh" + +#include "bmesh.hh" +#include "bmesh_idmap.hh" + +#define PBVH_STACK_FIXED_DEPTH 100 + +#include "DNA_meshdata_types.h" +#include "DNA_scene_enums.h" + /** \file * \ingroup bke */ - -namespace blender::draw::pbvh { -struct PBVHBatches; -} +struct CustomData; +struct PBVHTriBuf; struct PBVHGPUFormat; -struct BMVert; -struct BMFace; +struct MLoopTri; +using blender::Bounds; +using blender::float3; + +/* Axis-aligned bounding box */ +struct BB { + float bmin[3], bmax[3]; +}; + +/* Axis-aligned bounding box with centroid */ +struct BBC { + float bmin[3], bmax[3], bcentroid[3]; +}; /* NOTE: this structure is getting large, might want to split it into * union'd structs */ struct PBVHNode { /* Opaque handle for drawing code */ - blender::draw::pbvh::PBVHBatches *draw_batches = nullptr; + PBVHBatches *draw_batches = nullptr; /* Voxel bounds */ - blender::Bounds vb = {}; - blender::Bounds orig_vb = {}; + Bounds vb = {}; + Bounds orig_vb = {}; /* For internal nodes, the offset of the children in the PBVH * 'nodes' array. */ int children_offset = 0; + int subtree_tottri = 0; + + int depth = 0; /* List of primitives for this node. Semantics depends on * PBVH type: * - * - PBVH_FACES: Indices into the #PBVH::corner_tris array. + * - PBVH_FACES: Indices into the PBVH.looptri array. * - PBVH_GRIDS: Multires grid indices. * - PBVH_BMESH: Unused. See PBVHNode.bm_faces. * @@ -81,7 +106,7 @@ struct PBVHNode { * array. The array is sized to match 'totprim', and each of * the face's corners gets an index into the vert_indices * array, in the same order as the corners in the original - * triangle. + * MLoopTri. * * Used for leaf nodes in a mesh-based PBVH (not multires.) */ @@ -94,36 +119,48 @@ struct PBVHNode { /* Used for ray-casting: how close the bounding-box is to the ray point. */ float tmin = 0.0f; + /* Scalar displacements for sculpt mode's layer brush. */ + float *layer_disp = nullptr; + blender::Vector proxies; - /* Dyntopo */ - - /* Set of pointers to the BMFaces used by this node. - * NOTE: PBVH_BMESH only. Faces are always triangles - * (dynamic topology forcibly triangulates the mesh). + /* GSet of pointers to the BMFaces used by this node. + * NOTE: PBVH_BMESH only. */ - blender::Set bm_faces; - blender::Set bm_unique_verts; - blender::Set bm_other_verts; + blender::bke::dyntopo::DyntopoSet *bm_unique_verts = nullptr, *bm_other_verts = nullptr; + blender::bke::dyntopo::DyntopoSet *bm_faces = nullptr; - /* Deprecated. Stores original coordinates of triangles. */ - float (*bm_orco)[3] = nullptr; - int (*bm_ortri)[3] = nullptr; - BMVert **bm_orvert = nullptr; - int bm_tot_ortri = 0; + PBVHTriBuf *tribuf = nullptr; // all triangles + blender::Vector *tri_buffers = nullptr; - /* Used to store the brush color during a stroke and composite it over the original color */ - PBVHColorBufferNode color_buffer; + int updategen = 0; + +#ifdef PROXY_ADVANCED + ProxyVertArray proxyverts; +#endif PBVHPixelsNode pixels; /* Used to flash colors of updated node bounding boxes in * debug draw mode (when G.debug_value / bpy.app.debug_value is 889). */ int debug_draw_gen = 0; + int id = 0; }; +typedef enum { PBVH_IGNORE_UVS = 1 } PBVHFlags; +ENUM_OPERATORS(PBVHFlags, PBVH_IGNORE_UVS); + +typedef struct PBVHBMeshLog PBVHBMeshLog; +struct DMFlagMat; + struct PBVH { PBVHPublic header; + PBVHFlags flags; + eAttrCorrectMode distort_correction_mode; + + int idgen; + + bool dyntopo_stop; blender::Vector nodes; @@ -131,12 +168,15 @@ struct PBVH { blender::Array prim_indices; int totprim; int totvert; + int totloop; + blender::OffsetIndices faces; + int faces_num; /* Do not use directly, use BKE_pbvh_num_faces. */ int leaf_limit; int pixel_leaf_limit; int depth_limit; - /* Mesh data. The evaluated deform mesh for mesh sculpting, and the base mesh for grids. */ + /* Mesh data */ Mesh *mesh; /** Local array used when not sculpting base mesh positions directly. */ @@ -149,17 +189,43 @@ struct PBVH { blender::MutableSpan vert_positions; blender::Span vert_normals; blender::Span face_normals; + blender::Span edges; + bool *hide_vert; + bool *hide_poly; /** Only valid for polygon meshes. */ - blender::OffsetIndices faces; blender::Span corner_verts; + blender::Span corner_edges; + const bool *sharp_edges; + const bool *seam_edges; + + CustomData *vert_data; + CustomData *corner_data; + CustomData *face_data; + /* Owned by the #PBVH, because after deformations they have to be recomputed. */ blender::Array corner_tris; blender::Span corner_tri_faces; + blender::Span origco, origno; + + /* Sculpt flags*/ + uint8_t *sculpt_flags; + + /* Cached vertex valences */ + int *valence; + + int face_sets_color_seed; + int face_sets_color_default; + float *face_areas; /* float2 vector, double buffered to avoid thread contention */ + int face_area_i; + /* Grid Data */ CCGKey gridkey; - SubdivCCG *subdiv_ccg; + CCGElem **grids; + blender::Span grid_to_face_map; + int totgrid; + blender::BitGroupVector<> *grid_hidden; #ifdef PERFCNTRS int perf_modified; @@ -171,36 +237,81 @@ struct PBVH { /* Dynamic topology */ float bm_max_edge_len; float bm_min_edge_len; + float bm_detail_range; + struct BMIdMap *bm_idmap; + + int cd_flag; + int cd_valence; + int cd_origco, cd_origno; int cd_vert_node_offset; int cd_face_node_offset; + int cd_vert_mask_offset; + int cd_faceset_offset; + int cd_face_area; + int cd_vcol_offset; + + int totuv; float planes[6][4]; int num_planes; - BMLog *bm_log; + int symmetry; + BMLog *bm_log; + struct SubdivCCG *subdiv_ccg; + + bool need_full_render; /* Set by pbvh drawing for PBVH_BMESH. */ + + int balance_counter; + int stroke_id; + + bool invalid; blender::GroupedSpan vert_to_face_map; + blender::GroupedSpan vert_to_edge_map; CustomDataLayer *color_layer; blender::bke::AttrDomain color_domain; - /* Initialize this to true, instead of waiting for a draw engine - * to set it. Prevents a crash in draw manager instancing code. - * TODO: This is fragile, another solution should be found. */ - bool is_drawing = true; + bool is_drawing; /* Used by DynTopo to invalidate the draw cache. */ - bool draw_cache_invalid = true; + bool draw_cache_invalid; + + int *boundary_flags, *edge_boundary_flags; + int cd_boundary_flag, cd_edge_boundary; + int cd_curvature_dir; PBVHGPUFormat *vbo_id; PBVHPixels pixels; + bool show_orig; + float sharp_angle_limit; + + blender::BitVector<> *vert_boundary_map; }; /* pbvh.cc */ -namespace blender::bke::pbvh { +void BB_reset(BB *bb); +void BB_zero(BB *bb); +/** + * Expand the bounding box to include a new coordinate. + */ +void BB_expand(BB *bb, const float co[3]); +/** + * Expand the bounding box to include another bounding box. + */ +void BB_expand_with_bb(BB *bb, const BB *bb2); +void BBC_update_centroid(BBC *bbc); +/** + * Return 0, 1, or 2 to indicate the widest axis of the bounding box. + */ +int BB_widest_axis(const BB *bb); +void BB_intersect(BB *r_out, BB *a, BB *b); +float BB_volume(const BB *bb); + +namespace blender::bke::pbvh { bool ray_face_intersection_quad(const float ray_start[3], IsectRayPrecalc *isect_precalc, const float t0[3], @@ -230,30 +341,157 @@ bool ray_face_nearest_tri(const float ray_start[3], const float t2[3], float *r_depth, float *r_dist_sq); +} // namespace blender::bke::pbvh + +void pbvh_update_BB_redraw(PBVH *bvh, PBVHNode **nodes, int totnode, int flag); + +namespace blender::bke::pbvh { +bool ray_face_intersection_depth_tri(const float ray_start[3], + struct IsectRayPrecalc *isect_precalc, + const float t0[3], + const float t1[3], + const float t2[3], + float *r_depth, + int *hit_count); +} /* pbvh_bmesh.cc */ -bool bmesh_node_raycast(PBVHNode *node, - const float ray_start[3], - const float ray_normal[3], - IsectRayPrecalc *isect_precalc, - float *dist, - bool use_original, - PBVHVertRef *r_active_vertex, - float *r_face_normal); -bool bmesh_node_nearest_to_ray(PBVHNode *node, - const float ray_start[3], - const float ray_normal[3], - float *depth, - float *dist_sq, - bool use_original); +/* pbvh_bmesh.c */ +bool pbvh_bmesh_node_raycast(SculptSession *ss, + PBVH *pbvh, + PBVHNode *node, + const float ray_start[3], + const float ray_normal[3], + struct IsectRayPrecalc *isect_precalc, + int *hit_count, + float *depth, + bool use_original, + PBVHVertRef *r_active_vertex_index, + PBVHFaceRef *r_active_face_index, + float *r_face_normal, + int stroke_id); -void bmesh_normals_update(Span nodes); +bool pbvh_bmesh_node_nearest_to_ray(SculptSession *ss, + PBVH *pbvh, + PBVHNode *node, + const float ray_start[3], + const float ray_normal[3], + float *depth, + float *dist_sq, + bool use_original, + int stroke_id); + +void pbvh_update_free_all_draw_buffers(PBVH *pbvh, PBVHNode *node); + +BLI_INLINE int pbvh_bmesh_node_index_from_vert(PBVH *pbvh, const BMVert *key) +{ + const int node_index = BM_ELEM_CD_GET_INT((const BMElem *)key, pbvh->cd_vert_node_offset); + return node_index; +} + +BLI_INLINE PBVHNode *pbvh_bmesh_node_from_vert(PBVH *pbvh, const BMVert *key) +{ + int ni = pbvh_bmesh_node_index_from_vert(pbvh, key); + + return ni >= 0 ? &pbvh->nodes[ni] : nullptr; +} + +BLI_INLINE PBVHNode *pbvh_bmesh_node_from_face(PBVH *pbvh, const BMFace *key) +{ + int ni = BM_ELEM_CD_GET_INT(key, pbvh->cd_face_node_offset); + return ni >= 0 && ni < pbvh->nodes.size() ? &pbvh->nodes[ni] : nullptr; +} + +bool pbvh_bmesh_node_limit_ensure(PBVH *pbvh, int node_index); + +// #define PBVH_BMESH_DEBUG + +#ifdef PBVH_BMESH_DEBUG +void pbvh_bmesh_check_nodes(PBVH *pbvh); +void pbvh_bmesh_check_nodes_simple(PBVH *pbvh); +#else +# define pbvh_bmesh_check_nodes(pbvh) +# define pbvh_bmesh_check_nodes_simple(pbvh) +#endif + +void bke_pbvh_insert_face_finalize(PBVH *pbvh, BMFace *f, const int ni); +void bke_pbvh_insert_face(PBVH *pbvh, struct BMFace *f); + +inline bool pbvh_check_vert_boundary_bmesh(PBVH *pbvh, BMVert *v) +{ + int flag = BM_ELEM_CD_GET_INT(v, pbvh->cd_boundary_flag); + + if (flag & (SCULPT_BOUNDARY_NEEDS_UPDATE | SCULPT_BOUNDARY_UPDATE_UV)) { + blender::bke::pbvh::update_vert_boundary_bmesh(pbvh->cd_faceset_offset, + pbvh->cd_vert_node_offset, + pbvh->cd_face_node_offset, + pbvh->cd_vcol_offset, + pbvh->cd_boundary_flag, + pbvh->cd_flag, + pbvh->cd_valence, + v, + &pbvh->header.bm->ldata, + pbvh->sharp_angle_limit); + return true; + } + else if (flag & SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE) { + blender::bke::pbvh::update_sharp_vertex_bmesh( + v, pbvh->cd_boundary_flag, pbvh->sharp_angle_limit); + } + + return false; +} + +inline bool pbvh_check_edge_boundary_bmesh(PBVH *pbvh, struct BMEdge *e) +{ + int flag = BM_ELEM_CD_GET_INT(e, pbvh->cd_edge_boundary); + + if (flag & (SCULPT_BOUNDARY_NEEDS_UPDATE | SCULPT_BOUNDARY_UPDATE_UV | + SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE)) + { + blender::bke::pbvh::update_edge_boundary_bmesh(e, + pbvh->cd_faceset_offset, + pbvh->cd_edge_boundary, + pbvh->cd_flag, + pbvh->cd_valence, + &pbvh->header.bm->ldata, + pbvh->sharp_angle_limit); + return true; + } + + return false; +} + +void pbvh_bmesh_check_other_verts(PBVHNode *node); +void pbvh_bmesh_normals_update(PBVH *pbvh, blender::Span nodes); + +namespace blender::bke::pbvh { /* pbvh_pixels.hh */ - void node_pixels_free(PBVHNode *node); void pixels_free(PBVH *pbvh); -void free_draw_buffers(PBVH &pbvh, PBVHNode *node); +void free_draw_buffers(PBVH *pbvh, PBVHNode *node); } // namespace blender::bke::pbvh + +BLI_INLINE bool pbvh_boundary_needs_update_bmesh(PBVH *pbvh, BMVert *v) +{ + int *flags = (int *)BM_ELEM_CD_GET_VOID_P(v, pbvh->cd_boundary_flag); + + return *flags & SCULPT_BOUNDARY_NEEDS_UPDATE; +} + +template inline void pbvh_boundary_update_bmesh(PBVH *pbvh, T *elem) +{ + int *flags; + + if constexpr (std::is_same_v) { + flags = (int *)BM_ELEM_CD_GET_VOID_P(elem, pbvh->cd_boundary_flag); + } + else { + flags = (int *)BM_ELEM_CD_GET_VOID_P(elem, pbvh->cd_edge_boundary); + } + + *flags |= SCULPT_BOUNDARY_NEEDS_UPDATE; +} diff --git a/source/blender/blenkernel/intern/pbvh_pixels_copy.cc b/source/blender/blenkernel/intern/pbvh_pixels_copy.cc index ad65e7ab8a4..eec224d51f4 100644 --- a/source/blender/blenkernel/intern/pbvh_pixels_copy.cc +++ b/source/blender/blenkernel/intern/pbvh_pixels_copy.cc @@ -9,6 +9,7 @@ #include "BLI_math_vector.hh" #include "BLI_task.hh" #include "BLI_vector.hh" +#include "BLI_string_ref.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" diff --git a/source/blender/blenlib/BLI_bounds.hh b/source/blender/blenlib/BLI_bounds.hh index 35102be81f4..8e06e3323a3 100644 --- a/source/blender/blenlib/BLI_bounds.hh +++ b/source/blender/blenlib/BLI_bounds.hh @@ -20,6 +20,41 @@ namespace blender { namespace bounds { +template [[nodiscard]] Bounds expand(Bounds bb, const T b) +{ + Bounds expanded; + expanded.min = math::min(bb.min, b); + expanded.max = math::max(bb.max, b); + return expanded; +} + +template +F volume(const Bounds bb) +{ + F area = F(1); + for (int i = 0; i < size; i++) { + area *= bb.max[i] - bb.min[1]; + } + + return area; +} + +template +Bounds intersect(Bounds a, Bounds b) +{ + Bounds out; + + for (int i = 0; i < size; i++) { + out.min[i] = std::max(a.min[i], b.min[i]); + out.max[i] = std::min(a.max[i], b.max[i]); + + if (out.max[i] < out.min[i]) { + out.max[i] = out.min[i] = F(0); + } + } + + return out; +} template [[nodiscard]] inline Bounds merge(const Bounds &a, const Bounds &b) { @@ -187,5 +222,4 @@ inline void Bounds::pad(const PaddingT &padding) this->min = this->min - padding; this->max = this->max + padding; } - } // namespace blender diff --git a/source/blender/blenlib/BLI_compiler_attrs.h b/source/blender/blenlib/BLI_compiler_attrs.h index 0f48ca0dd2d..d53ad528d14 100644 --- a/source/blender/blenlib/BLI_compiler_attrs.h +++ b/source/blender/blenlib/BLI_compiler_attrs.h @@ -18,6 +18,12 @@ /* hint to mark function arguments expected to be non-null * if no arguments are given to the macro, all of pointer * arguments would be expected to be non-null + * + * ONE-INDEXED! + * + * Example: + * + * void func(void *a, void *b, void *b) ATTR_NONNULL(1, 2, 3) */ #ifdef __GNUC__ # define ATTR_NONNULL(args...) __attribute__((nonnull(args))) @@ -84,6 +90,20 @@ # define ATTR_ALIGN(x) __attribute__((aligned(x))) #endif +/* NotForPR: Needed for debugging but already been rejected in + * a standalone PR. + * Disable optimization for a function (for debugging use only!) + */ +#ifdef __clang__ +# define ATTR_NO_OPT __attribute__((optnone)) +#elif defined(_MSC_VER) +# define ATTR_NO_OPT __pragma(optimize("", off)) +#elif defined(__GNUC__) +# define ATTR_NO_OPT __attribute__((optimize("O0"))) +#else +# define ATTR_NO_OPT +#endif + /* Alignment directive */ #ifdef _WIN64 # define BLI_ALIGN_STRUCT __declspec(align(64)) diff --git a/source/blender/blenlib/BLI_heap_minmax.hh b/source/blender/blenlib/BLI_heap_minmax.hh new file mode 100644 index 00000000000..22df09d75b8 --- /dev/null +++ b/source/blender/blenlib/BLI_heap_minmax.hh @@ -0,0 +1,638 @@ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup bli + * \brief A min-heap / priority queue ADT + */ + +#include "BLI_index_range.hh" +#include "BLI_memory_utils.hh" +#include "BLI_vector.hh" + +using blender::IndexRange; +using blender::Vector; + +#include + +//#define BLI_MINMAX_HEAP_VALIDATE + +namespace blender { +template class HeapValueIter { + Span nodes_; + int i_ = 0; + + public: + HeapValueIter(Vector &nodes) : nodes_(nodes) {} + HeapValueIter(Span &nodes) : nodes_(nodes) {} + HeapValueIter(const HeapValueIter &b) : nodes_(b.nodes_), i_(b.i_) {} + + bool operator==(const HeapValueIter &b) + { + return b.i_ == i_; + } + + bool operator!=(const HeapValueIter &b) + { + return b.i_ != i_; + } + + Value operator*() + { + return nodes_[i_].value; + } + + HeapValueIter &operator++() + { + i_++; + + return *this; + } + + HeapValueIter begin() + { + return HeapValueIter(nodes_); + } + + HeapValueIter end() + { + HeapValueIter ret(nodes_); + ret.i_ = std::max(int(nodes_.size()) - 1, 0); + + return ret; + } +}; + +template + +class MinMaxHeap { + struct MinMaxHeapNode { + Value value; + float weight; + + int child1 = -1, child2 = -1, parent = -1; + }; + + public: + MinMaxHeap(int reserved = 0) + { + if (reserved > 0) { + nodes.reserve(reserved); + } + } + + ~MinMaxHeap() {} + + bool valid_recurse(int i) + { + bool ret = true; + + MinMaxHeapNode &node = nodes[i]; + int level = i & 1; + + if (node.child1 != -1) { + MinMaxHeapNode &child = nodes[node.child1]; + if (child.weight != node.weight && (child.weight < node.weight) ^ level) { + printf("error: node %d[%f] is less than node %d[%f]\n", + i, + node.weight, + node.child1, + child.weight); + } + + ret = ret && valid_recurse(node.child1); + } + + if (node.child2 != -1) { + MinMaxHeapNode &child = nodes[node.child2]; + + if (child.weight != node.weight && (child.weight < node.weight) ^ level) { + printf("error: node %d[%f] is less than node %d[%f]\n", + i, + node.weight, + node.child2, + child.weight); + } + + ret = ret && valid_recurse(node.child2); + } + + return ret; + } + + bool is_valid() + { + + if (nodes.size() == 0) { + return true; + } + + return valid_recurse(0); + } + + HeapValueIter values() + { + return HeapValueIter(nodes); + } + + MinMaxHeapNode *insert(float weight, Value value) + { + MinMaxHeapNode *node = heap_make_node(); + + node->value = value; + node->weight = weight; + + if (nodes.size() == 1) { + return node; + } + + int i = node - nodes.data(); + + node->parent = (i - 1) >> 1; + + MinMaxHeapNode *parent = &nodes[node->parent]; + if (parent->child1 == -1) { + parent->child1 = i; + } + else { + parent->child2 = i; + } + + MinMaxHeapNode *ret = heap_push_up(node); + +#ifdef BLI_MINMAX_HEAP_VALIDATE + if (!is_valid()) { + printf("invalid heap!\n"); + } +#endif + + return ret; + } + + void insert_or_update(MinMaxHeapNode **node_p, float weight, Value value) + { + MinMaxHeapNode *node = *node_p; + + if (!node) { + *node_p = insert(weight, value); + return; + } + + node = heap_push_down(node); + node = heap_push_up(node); + + *node_p = node; + } + + bool empty() const + { + return nodes.size() == 0; + } + + unsigned int len() const + { + return nodes.size(); + } + + float min_weight() + { + return nodes[0].weight; + } + + float max_weight() + { + return max().weight; + } + + Value peek_min(float *r_w = nullptr) + { + if (r_w) { + *r_w = nodes[0].weight; + } + + return nodes[0].value; + } + + Value pop_min(float *r_w = nullptr) + { + if (nodes.size() == 1) { + if (r_w) { + *r_w = nodes[0].weight; + } + return nodes.pop_last().value; + } + +#ifdef BLI_MINMAX_HEAP_VALIDATE + if (!is_valid()) { + printf("invalid heap!\n"); + } +#endif + + Value ret = nodes[0].value; + if (r_w) { + *r_w = nodes[0].weight; + } + + MinMaxHeapNode last = heap_pop_last(); + + nodes[0].weight = last.weight; + nodes[0].value = last.value; + + heap_push_down(&nodes[0]); + +#ifdef BLI_MINMAX_HEAP_VALIDATE + if (!is_valid()) { + printf("invalid heap!\n"); + } +#endif + + return ret; + } + + Value peek_max(float *r_w = nullptr) + { + if (nodes.size() == 1) { + if (r_w) { + *r_w = nodes[0].weight; + } + return nodes[0].value; + } + + MinMaxHeapNode &node = max(); + if (r_w) { + *r_w = node.weight; + } + + return node.value; + } + + Value pop_max(float *r_w = nullptr) + { + if (nodes.size() == 1) { + if (r_w) { + *r_w = nodes[0].weight; + } + return nodes.pop_last().value; + } + + MinMaxHeapNode &node = max(); + +#ifdef BLI_MINMAX_HEAP_VALIDATE + if (!is_valid()) { + printf("invalid heap!\n"); + } +#endif + + Value ret = node.value; + if (r_w) { + *r_w = node.weight; + } + + MinMaxHeapNode last = heap_pop_last(); + + node.weight = last.weight; + node.value = last.value; + + heap_push_down(&node); + +#ifdef BLI_MINMAX_HEAP_VALIDATE + if (!is_valid()) { + printf("invalid heap!\n"); + } +#endif + + return ret; + } + + MinMaxHeapNode *node_weight_update(MinMaxHeapNode *node, float weight) + { + node->weight = weight; + + node = heap_push_down(node); + node = heap_push_up(node); + + return node; + } + + MinMaxHeapNode *node_weight_update_value(MinMaxHeapNode *node, float weight, Value value) + { + node->weight = weight; + node->value = value; + + node = heap_push_down(node); + node = heap_push_up(node); + + return node; + } + + private: + MinMaxHeapNode &min() + { + return nodes[0]; + } + + MinMaxHeapNode &max() + { + if (nodes.size() == 1) { + return nodes[0]; + } + + MinMaxHeapNode &root = nodes[0]; + if (root.child1 != -1 && root.child2 != -1) { + if (nodes[nodes[0].child1].weight > nodes[nodes[0].child2].weight) { + return nodes[nodes[0].child1]; + } + + return nodes[nodes[0].child2]; + } + else if (root.child1 != -1) { + return nodes[nodes[0].child1]; + } + else { + return nodes[nodes[0].child2]; + } + } + + MinMaxHeapNode *heap_make_node() + { + nodes.resize(nodes.size() + 1); + return &nodes[nodes.size() - 1]; + } + + MinMaxHeapNode *heap_descent_min(MinMaxHeapNode *node) + { + if (node->child1 != -1 && node->child2 != -1) { + MinMaxHeapNode *n1 = &nodes[node->child1]; + MinMaxHeapNode *n2 = &nodes[node->child2]; + + n1 = heap_descent_min2(n1); + n2 = heap_descent_min2(n2); + + return n1->value < n2->value ? n1 : n2; + } + else if (node->child1 != -1) { + return heap_descent_min2(&nodes[node->child1]); + } + else if (node->child2 != -1) { + return heap_descent_min2(&nodes[node->child2]); + } + + return nullptr; + } + + MinMaxHeapNode *heap_descent_min2(MinMaxHeapNode *n) + { + if (n->child1 != -1 && n->child2 != -1) { + MinMaxHeapNode *n1 = &nodes[n->child1]; + MinMaxHeapNode *n2 = &nodes[n->child2]; + + return n1->weight < n2->weight ? n1 : n2; + } + else if (n->child1 != -1) { + return &nodes[n->child1]; + } + else if (n->child2 != -1) { + return &nodes[n->child2]; + } + + return n; + } + + MinMaxHeapNode *heap_descent_max2(MinMaxHeapNode *n) + { + if (n->child1 != -1 && n->child2 != -1) { + MinMaxHeapNode *n1 = &nodes[n->child1]; + MinMaxHeapNode *n2 = &nodes[n->child2]; + + return n1->weight > n2->weight ? n1 : n2; + } + else if (n->child1 != -1) { + return &nodes[n->child1]; + } + else if (n->child2 != -1) { + return &nodes[n->child2]; + } + + return n; + } + + /* find node grandchild */ + MinMaxHeapNode *heap_descent_max(MinMaxHeapNode *node) + { + if (node->child1 != -1 && node->child2 != -1) { + MinMaxHeapNode *n1 = &nodes[node->child1]; + MinMaxHeapNode *n2 = &nodes[node->child2]; + + n1 = heap_descent_max2(n1); + n2 = heap_descent_max2(n2); + + return n1->weight > n2->weight ? n1 : n2; + } + else if (node->child1 != -1) { + return heap_descent_max2(&nodes[node->child1]); + } + else if (node->child2 != -1) { + return heap_descent_max2(&nodes[node->child2]); + } + + return nullptr; + } + + MinMaxHeapNode *heap_push_down_min(MinMaxHeapNode *node) + { + MinMaxHeapNode *ret = node; + MinMaxHeapNode *node2 = heap_descent_min(node); + + if (!node2) { + return node; + } + + /* Is node2 a grandchild? */ + if (node2->parent != node - nodes.data()) { + MinMaxHeapNode *parent = &nodes[node2->parent]; + + if (node2->weight < node->weight) { + std::swap(node2->weight, node->weight); + std::swap(node2->value, node->value); + ret = node2; + + if (node2->weight > parent->weight) { + std::swap(node2->weight, parent->weight); + std::swap(node2->value, parent->value); + ret = parent; + } + + ret = heap_push_down(node2); + } + } + else if (node2->weight < node->weight) { + std::swap(node2->weight, node->weight); + std::swap(node2->value, node->value); + ret = node2; + } + + return ret; + } + + MinMaxHeapNode *heap_push_down_max(MinMaxHeapNode *node) + { + MinMaxHeapNode *ret = node; + MinMaxHeapNode *node2 = heap_descent_max(node); + + if (!node2) { + return node; + } + + /* Is node2 a grandchild? */ + if (node2->parent != node - nodes.data()) { + MinMaxHeapNode *parent = &nodes[node2->parent]; + + if (node2->weight > node->weight) { + std::swap(node2->weight, node->weight); + std::swap(node2->value, node->value); + ret = node2; + + if (node2->weight < parent->weight) { + std::swap(node2->weight, parent->weight); + std::swap(node2->value, parent->value); + ret = parent; + } + + ret = heap_push_down(node2); + } + } + else if (node2->weight > node->weight) { + std::swap(node2->weight, node->weight); + std::swap(node2->value, node->value); + ret = node2; + } + + return ret; + } + + int heap_get_level(const MinMaxHeapNode *node) + { + int i = 0; + + while (node->parent != -1) { + node = &nodes[node->parent]; + i++; + } + + return i; + } + + MinMaxHeapNode *heap_push_down(MinMaxHeapNode *node) + { + int i = heap_get_level(node); + + if (i & 1) { + return heap_push_down_max(node); + } + else { + return heap_push_down_min(node); + } + } + + MinMaxHeapNode *heap_push_up_min(MinMaxHeapNode *node) + { + while (node->parent != -1) { + MinMaxHeapNode *parent = &nodes[node->parent]; + + if (parent->parent != -1 && node->weight < nodes[parent->parent].weight) { + parent = &nodes[parent->parent]; + + std::swap(node->weight, parent->weight); + std::swap(node->value, parent->value); + + node = parent; + } + else { + break; + } + } + + return node; + } + + MinMaxHeapNode *heap_push_up_max(MinMaxHeapNode *node) + { + while (node->parent != -1) { + MinMaxHeapNode *parent = &nodes[node->parent]; + + if (parent->parent != -1 && node->weight > nodes[parent->parent].weight) { + parent = &nodes[parent->parent]; + + std::swap(node->weight, parent->weight); + std::swap(node->value, parent->value); + + node = parent; + } + else { + break; + } + } + + return node; + } + + MinMaxHeapNode *heap_push_up(MinMaxHeapNode *node) + { + int i = heap_get_level(node); + + if ((i & 1) == 0) { + MinMaxHeapNode *parent = &nodes[node->parent]; + + if (node->weight > parent->weight) { + std::swap(node->weight, parent->weight); + std::swap(node->value, parent->value); + + return heap_push_up_max(parent); + } + else { + return heap_push_up_min(node); + } + } + else { + MinMaxHeapNode *parent = &nodes[node->parent]; + + if (node->weight < parent->weight) { + std::swap(node->weight, parent->weight); + std::swap(node->value, parent->value); + + return heap_push_up_min(parent); + } + else { + return heap_push_up_max(node); + } + } + + return node; + } + + MinMaxHeapNode heap_pop_last() + { + int index = nodes.size() - 1; + + MinMaxHeapNode last = nodes[index]; + if (last.parent != -1) { + MinMaxHeapNode &parent = nodes[last.parent]; + if (parent.child1 == index) { + parent.child1 = -1; + } + else { + parent.child2 = -1; + } + } + + nodes.pop_last(); + return last; + } + + Vector nodes; +}; +} // namespace blender diff --git a/source/blender/blenlib/BLI_mempool.h b/source/blender/blenlib/BLI_mempool.h index 1a9c0d08362..f27c31781d2 100644 --- a/source/blender/blenlib/BLI_mempool.h +++ b/source/blender/blenlib/BLI_mempool.h @@ -118,6 +118,8 @@ void BLI_mempool_iternew(BLI_mempool *pool, BLI_mempool_iter *iter) ATTR_NONNULL */ void *BLI_mempool_iterstep(BLI_mempool_iter *iter) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); +size_t BLI_mempool_get_size(BLI_mempool *pool); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index e8ef92025c4..a558697dc03 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -248,6 +248,7 @@ set(SRC BLI_hash_tables.hh BLI_heap.h BLI_heap_simple.h + BLI_heap_minmax.hh BLI_implicit_sharing.h BLI_implicit_sharing.hh BLI_implicit_sharing_ptr.hh diff --git a/source/blender/blenlib/intern/BLI_mempool.c b/source/blender/blenlib/intern/BLI_mempool.c index 610c39176ed..e489f363476 100644 --- a/source/blender/blenlib/intern/BLI_mempool.c +++ b/source/blender/blenlib/intern/BLI_mempool.c @@ -920,3 +920,16 @@ void BLI_mempool_set_memory_debug(void) mempool_debug_memset = true; } #endif + +size_t BLI_mempool_get_size(BLI_mempool *pool) +{ + unsigned int chunk_count = 0; + BLI_mempool_chunk *chunk = pool->chunks; + + while (chunk) { + chunk = chunk->next; + chunk_count++; + } + + return (size_t)(chunk_count * pool->csize); +} diff --git a/source/blender/blenlib/intern/cpp_types.cc b/source/blender/blenlib/intern/cpp_types.cc index c59967ae94e..2cc56ad5c91 100644 --- a/source/blender/blenlib/intern/cpp_types.cc +++ b/source/blender/blenlib/intern/cpp_types.cc @@ -50,7 +50,9 @@ BLI_CPP_TYPE_MAKE(bool, CPPTypeFlags::BasicType) BLI_CPP_TYPE_MAKE(float, CPPTypeFlags::BasicType) BLI_CPP_TYPE_MAKE(blender::float2, CPPTypeFlags::BasicType) BLI_CPP_TYPE_MAKE(blender::float3, CPPTypeFlags::BasicType) +BLI_CPP_TYPE_MAKE(blender::float4, CPPTypeFlags::BasicType) BLI_CPP_TYPE_MAKE(blender::float4x4, CPPTypeFlags::BasicType) +BLI_CPP_TYPE_MAKE(blender::uchar4, CPPTypeFlags::BasicType) BLI_CPP_TYPE_MAKE(int8_t, CPPTypeFlags::BasicType) BLI_CPP_TYPE_MAKE(int16_t, CPPTypeFlags::BasicType) @@ -81,7 +83,9 @@ void register_cpp_types() BLI_CPP_TYPE_REGISTER(float); BLI_CPP_TYPE_REGISTER(blender::float2); BLI_CPP_TYPE_REGISTER(blender::float3); + BLI_CPP_TYPE_REGISTER(blender::float4); BLI_CPP_TYPE_REGISTER(blender::float4x4); + BLI_CPP_TYPE_REGISTER(blender::uchar4); BLI_CPP_TYPE_REGISTER(int8_t); BLI_CPP_TYPE_REGISTER(int16_t); diff --git a/source/blender/blenloader/intern/versioning_300.cc b/source/blender/blenloader/intern/versioning_300.cc index 633e0100ffd..72dd3068ce1 100644 --- a/source/blender/blenloader/intern/versioning_300.cc +++ b/source/blender/blenloader/intern/versioning_300.cc @@ -35,6 +35,7 @@ #include "DNA_constraint_types.h" #include "DNA_curve_types.h" #include "DNA_curves_types.h" +#include "DNA_defaults.h" #include "DNA_genfile.h" #include "DNA_gpencil_modifier_types.h" #include "DNA_light_types.h" @@ -59,6 +60,8 @@ #include "BKE_animsys.h" #include "BKE_armature.hh" #include "BKE_asset.hh" +#include "BKE_attribute.h" +#include "BKE_brush.hh" #include "BKE_attribute.hh" #include "BKE_collection.hh" #include "BKE_colortools.hh" @@ -4374,6 +4377,158 @@ void blo_do_versions_300(FileData *fd, Library * /*lib*/, Main *bmain) } } + if (!DNA_struct_member_exists(fd->filesdna, "Brush", "float", "hard_corner_pin")) { + LISTBASE_FOREACH (Brush *, brush, &bmain->brushes) { + brush->hard_corner_pin = 1.0f; + } + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + if (scene->toolsettings) { + scene->toolsettings->unified_paint_settings.hard_corner_pin = 1.0f; + scene->toolsettings->unified_paint_settings.flag |= UNIFIED_PAINT_HARD_CORNER_PIN | + UNIFIED_PAINT_FLAG_HARD_EDGE_MODE; + } + } + } + + if (!DNA_struct_member_exists( + fd->filesdna, "UnifiedPaintSettings", "float", "sharp_angle_limit")) { + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + if (!scene->toolsettings) { + continue; + } + + scene->toolsettings->unified_paint_settings.flag |= UNIFIED_PAINT_FLAG_HARD_EDGE_MODE | + UNIFIED_PAINT_HARD_CORNER_PIN | + UNIFIED_PAINT_FLAG_SHARP_ANGLE_LIMIT; + scene->toolsettings->unified_paint_settings.sharp_angle_limit = + (DNA_struct_default_get(UnifiedPaintSettings))->sharp_angle_limit; + } + + LISTBASE_FOREACH (Brush *, brush, &bmain->brushes) { + brush->sharp_angle_limit = (DNA_struct_default_get(Brush))->sharp_angle_limit; + } + } + + if (!DNA_struct_member_exists( + fd->filesdna, "UnifiedPaintSettings", "int", "smooth_boundary_flag")) { + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + if (!scene->toolsettings) { + continue; + } + + scene->toolsettings->unified_paint_settings.smooth_boundary_flag = + (DNA_struct_default_get(UnifiedPaintSettings))->smooth_boundary_flag; + } + } + + if (!DNA_struct_member_exists(fd->filesdna, "DynTopoSettings", "float", "quality")) { + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + if (!scene->toolsettings || !scene->toolsettings->sculpt) { + continue; + } + + scene->toolsettings->sculpt->dyntopo.quality = 1.0f; + } + + LISTBASE_FOREACH (Brush *, brush, &bmain->brushes) { + brush->dyntopo.quality = 1.0f; + } + } + + if (!DNA_struct_member_exists(fd->filesdna, "Sculpt", "DynTopoSettings", "dyntopo")) { + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + if (!scene->toolsettings || !scene->toolsettings->sculpt) { + continue; + } + + Sculpt *sculpt = scene->toolsettings->sculpt; + sculpt->dyntopo = *DNA_struct_default_get(DynTopoSettings); + + DynTopoSettings *ds = &sculpt->dyntopo; + + ds->detail_percent = sculpt->detail_percent; + ds->detail_size = sculpt->detail_size; + ds->constant_detail = sculpt->constant_detail; + + sculpt->flags |= SCULPT_DYNTOPO_ENABLED; + + ds->flag = 0; + if (sculpt->flags & SCULPT_DYNTOPO_SUBDIVIDE) { + ds->flag |= DYNTOPO_SUBDIVIDE; + } + if (sculpt->flags & SCULPT_DYNTOPO_COLLAPSE) { + ds->flag |= DYNTOPO_COLLAPSE; + } + + ds->flag |= DYNTOPO_CLEANUP; + + if (sculpt->flags & SCULPT_DYNTOPO_DETAIL_CONSTANT) { + ds->mode = DYNTOPO_DETAIL_CONSTANT; + } + else if (sculpt->flags & SCULPT_DYNTOPO_DETAIL_BRUSH) { + ds->mode = DYNTOPO_DETAIL_BRUSH; + } + else if (sculpt->flags & SCULPT_DYNTOPO_DETAIL_MANUAL) { + ds->mode = DYNTOPO_DETAIL_MANUAL; + } + else { + ds->mode = DYNTOPO_DETAIL_RELATIVE; + } + } + } + + if (!DNA_struct_member_exists(fd->filesdna, "Brush", "DynTopoSettings", "dyntopo") || + !MAIN_VERSION_FILE_ATLEAST(bmain, 306, 4)) + { + LISTBASE_FOREACH (Brush *, brush, &bmain->brushes) { + DynTopoSettings *ds = &brush->dyntopo; + brush->dyntopo = *DNA_struct_default_get(DynTopoSettings); + + if (ELEM(brush->sculpt_tool, + SCULPT_TOOL_SMOOTH, + SCULPT_TOOL_DISPLACEMENT_ERASER, + SCULPT_TOOL_SLIDE_RELAX, + SCULPT_TOOL_ROTATE, + SCULPT_TOOL_GRAB, + SCULPT_TOOL_CLOTH, + SCULPT_TOOL_PAINT)) + { + brush->dyntopo.flag |= DYNTOPO_DISABLED; + } + + if (ELEM(brush->sculpt_tool, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_SLIDE_RELAX)) { + brush->flag2 |= BRUSH_SMOOTH_USE_AREA_WEIGHT; + } + + /* Some tools need special dyntopo overrides; copy from defaults. */ + if (ELEM(brush->sculpt_tool, SCULPT_TOOL_SNAKE_HOOK, SCULPT_TOOL_SIMPLIFY)) { + Brush dummy = {}; + dummy.sculpt_tool = brush->sculpt_tool; + + BKE_brush_sculpt_reset(&dummy); + if (dummy.curve) { + BKE_curvemapping_free(dummy.curve); + } + + brush->dyntopo = dummy.dyntopo; + } + } + } + + if (!DNA_struct_member_exists( + fd->filesdna, "UnifiedPaintSettings", "int", "smooth_boundary_flag")) { + LISTBASE_FOREACH (Brush *, brush, &bmain->brushes) { + if (ELEM(brush->sculpt_tool, + SCULPT_TOOL_MASK, + SCULPT_TOOL_DRAW_FACE_SETS, + SCULPT_TOOL_PAINT, + SCULPT_TOOL_SMEAR)) + { + brush->dyntopo.flag |= DYNTOPO_DISABLED; + } + } + } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 306, 3)) { /* Z bias for retopology overlay. */ if (!DNA_struct_member_exists(fd->filesdna, "View3DOverlay", "float", "retopology_offset")) { @@ -4389,8 +4544,8 @@ void blo_do_versions_300(FileData *fd, Library * /*lib*/, Main *bmain) } } - /* Use `SEQ_SINGLE_FRAME_CONTENT` flag instead of weird function to check if strip has multiple - * frames. */ + /* Use `SEQ_SINGLE_FRAME_CONTENT` flag instead of weird function to check if strip has + * multiple frames. */ LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { Editing *ed = SEQ_editing_get(scene); if (ed != nullptr) { @@ -4405,6 +4560,23 @@ void blo_do_versions_300(FileData *fd, Library * /*lib*/, Main *bmain) } } + /* Delete any mesh id layers from old versions of sculpt-dev. */ + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 306, 1)) { + LISTBASE_FOREACH (Mesh *, mesh, &bmain->meshes) { + CustomData *data = &mesh->vert_data; + + for (int i = 0; i < 4; i++, data++) { + for (int j = 0; j < data->totlayer; j++) { + /* CD_PROP_QUATERNION used to be CD_MESH_ID. */ + if (data->layers[j].type == 52) { + CustomData_free_layer(data, eCustomDataType(52), 0, j); + j--; + } + } + } + } + } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 306, 5)) { /* Some regions used to be added/removed dynamically. Ensure they are always there, there is a * `ARegionType.poll()` now. */ @@ -4413,7 +4585,8 @@ void blo_do_versions_300(FileData *fd, Library * /*lib*/, Main *bmain) LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { version_ensure_missing_regions(area, sl); - /* Ensure expected region state. Previously this was modified to hide/unhide regions. */ + /* Ensure expected region state. Previously this was modified to hide/unhide regions. + */ const ListBase *regionbase = (sl == area->spacedata.first) ? &area->regionbase : &sl->regionbase; diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index af92f7cf3dd..69ba1810d9c 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -1994,6 +1994,19 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) } } + if (!DNA_struct_member_exists( + fd->filesdna, "UnifiedPaintSettings", "char", "distort_correction_mode")) + { + UnifiedPaintSettings defaults = *DNA_struct_default_get(UnifiedPaintSettings); + + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + if (scene->toolsettings) { + scene->toolsettings->unified_paint_settings.distort_correction_mode = + defaults.distort_correction_mode; + } + } + } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 400, 8)) { LISTBASE_FOREACH (bAction *, act, &bmain->actions) { act->frame_start = max_ff(act->frame_start, MINAFRAMEF); @@ -2937,6 +2950,16 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) } if (!MAIN_VERSION_FILE_ATLEAST(bmain, 402, 3)) { + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + if (!scene->toolsettings) { + continue; + } + + UnifiedPaintSettings &ups = scene->toolsettings->unified_paint_settings; + ups.flag |= UNIFIED_PAINT_HARD_CORNER_PIN | UNIFIED_PAINT_FLAG_SHARP_ANGLE_LIMIT | + UNIFIED_PAINT_FLAG_HARD_EDGE_MODE; + } + constexpr int NTREE_EXECUTION_MODE_FULL_FRAME = 1; constexpr int NTREE_COM_GROUPNODE_BUFFER = 1 << 3; diff --git a/source/blender/bmesh/CMakeLists.txt b/source/blender/bmesh/CMakeLists.txt index af8741ce413..d64248c9cc9 100644 --- a/source/blender/bmesh/CMakeLists.txt +++ b/source/blender/bmesh/CMakeLists.txt @@ -57,6 +57,8 @@ set(SRC intern/bmesh_callback_generic.cc intern/bmesh_callback_generic.hh + intern/bmesh_collapse.cc + intern/bmesh_collapse.hh intern/bmesh_construct.cc intern/bmesh_construct.hh intern/bmesh_core.cc @@ -65,6 +67,8 @@ set(SRC intern/bmesh_delete.hh intern/bmesh_edgeloop.cc intern/bmesh_edgeloop.hh + intern/bmesh_idmap.cc + intern/bmesh_idmap.hh intern/bmesh_inline.hh intern/bmesh_interp.cc intern/bmesh_interp.hh @@ -157,6 +161,8 @@ set(SRC tools/bmesh_wireframe.hh bmesh_class.hh + bmesh_idmap.hh + bmesh_log.hh # public includes bmesh.hh @@ -170,7 +176,6 @@ set(LIB PRIVATE bf::dna PRIVATE bf::intern::clog PRIVATE bf::intern::guardedalloc - extern_rangetree PRIVATE bf::intern::atomic ) diff --git a/source/blender/bmesh/bmesh.hh b/source/blender/bmesh/bmesh.hh index f1b9a30641d..88784dfceea 100644 --- a/source/blender/bmesh/bmesh.hh +++ b/source/blender/bmesh/bmesh.hh @@ -195,7 +195,6 @@ #include "intern/bmesh_edgeloop.hh" #include "intern/bmesh_interp.hh" #include "intern/bmesh_iterators.hh" -#include "intern/bmesh_log.hh" #include "intern/bmesh_marking.hh" #include "intern/bmesh_mesh.hh" #include "intern/bmesh_mesh_convert.hh" diff --git a/source/blender/bmesh/bmesh_class.hh b/source/blender/bmesh/bmesh_class.hh index d4a7a822d6b..b73e9eb23b1 100644 --- a/source/blender/bmesh/bmesh_class.hh +++ b/source/blender/bmesh/bmesh_class.hh @@ -665,3 +665,8 @@ typedef bool (*BMLoopPairFilterFunc)(const BMLoop *, const BMLoop *, void *user_ /* Minimum number of elements before using threading. */ #define BM_THREAD_LIMIT 10000 + +template T BM_ELEM_CD_PTR(E *elem, const int cd_offset) +{ + return reinterpret_cast(BM_ELEM_CD_GET_VOID_P(elem, cd_offset)); +} diff --git a/source/blender/bmesh/bmesh_idmap.hh b/source/blender/bmesh/bmesh_idmap.hh new file mode 100644 index 00000000000..56435e8facc --- /dev/null +++ b/source/blender/bmesh/bmesh_idmap.hh @@ -0,0 +1 @@ +#include "intern/bmesh_idmap.hh" diff --git a/source/blender/bmesh/bmesh_log.hh b/source/blender/bmesh/bmesh_log.hh new file mode 100644 index 00000000000..08493cbbed5 --- /dev/null +++ b/source/blender/bmesh/bmesh_log.hh @@ -0,0 +1 @@ +#include "intern/bmesh_log.hh" diff --git a/source/blender/bmesh/intern/bmesh_collapse.cc b/source/blender/bmesh/intern/bmesh_collapse.cc new file mode 100644 index 00000000000..c17fc425b3e --- /dev/null +++ b/source/blender/bmesh/intern/bmesh_collapse.cc @@ -0,0 +1,789 @@ +/* SPDX-FileCopyrightText: + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup bmesh + */ + +#pragma once + +#include +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_meshdata_types.h" + +#include "BLI_compiler_attrs.h" +#include "BLI_compiler_compat.h" +#include "BLI_function_ref.hh" +#include "BLI_map.hh" +#include "BLI_math_vector_types.hh" +#include "BLI_set.hh" +#include "BLI_span.hh" +#include "BLI_utildefines.h" +#include "BLI_vector.hh" + +using blender::float2; +using blender::float3; +using blender::IndexRange; +using blender::Map; +using blender::MutableSpan; +using blender::Set; +using blender::Span; +using blender::Vector; + +#include +#include +#include +#include + +#include "bmesh.hh" +#include "bmesh_collapse.hh" +#include "bmesh_private.hh" +#include "bmesh_structure.hh" + +void bmesh_disk_edge_append(BMEdge *e, BMVert *v); +void bmesh_radial_loop_append(BMEdge *e, BMLoop *l); +void bm_kill_only_edge(BMesh *bm, BMEdge *e); +void bm_kill_only_loop(BMesh *bm, BMLoop *l); +void bm_kill_only_face(BMesh *bm, BMFace *f); + +//#define JVKE_DEBUG + +namespace blender::bmesh { +#ifdef JVKE_DEBUG + +static void bm_local_obj_free(char *str, char *fixed) +{ + if (str != fixed) { + MEM_freeN(str); + } +} + +# define LOCAL_OBJ_SIZE 512 + +static char *obj_append_line(const char *line, char *str, char *fixed, int *size, int *i) +{ + int len = (int)strlen(line); + + if (*i + len + 1 >= *size) { + *size += len + ((*size) >> 1); + + if (str == fixed) { + str = static_cast(MEM_mallocN(*size, "buf")); + memcpy(static_cast(str), fixed, LOCAL_OBJ_SIZE); + } + else { + str = static_cast(MEM_reallocN(str, *size)); + } + } + + memcpy(str + *i, line, len); + str[*i + len] = 0; + + *i += len; + + return str; +} + +/* NotForPr: saves an obj of the neighborhood around an edge prior to collapse + * into a buffer that can be read from a debugger. + */ +static char *bm_save_local_obj_text( + BMesh *, int depth, char buf[LOCAL_OBJ_SIZE], const char *fmt, ...) +{ + va_list vl; + va_start(vl, fmt); + + buf[0] = 0; + + Vector vs; + Vector initial_vs; + Vector es; + Vector initial_es; + Vector fs; + Vector initial_fs; + + Set visit; + + const char *c = fmt; + while (*c) { + if (*c == ' ' || *c == '\t') { + c++; + continue; + } + + void *ptr = va_arg(vl, void *); + + switch (*c) { + case 'v': + vs.append(static_cast(ptr)); + initial_vs.append(static_cast(ptr)); + break; + case 'e': + es.append(static_cast(ptr)); + initial_es.append(static_cast(ptr)); + break; + case 'f': + fs.append(static_cast(ptr)); + initial_fs.append(static_cast(ptr)); + break; + } + + c++; + } + + va_end(vl); + + int tag = 4; + for (BMFace *f : fs) { + BMLoop *l = f->l_first; + + do { + l->v->head.api_flag &= ~tag; + l->e->head.api_flag &= ~tag; + } while ((l = l->next) != f->l_first); + } + + for (BMEdge *e : es) { + e->v1->head.api_flag &= ~tag; + e->v2->head.api_flag &= ~tag; + } + + for (BMVert *v : vs) { + v->head.api_flag |= tag; + } + + for (BMEdge *e : es) { + if (!(e->v1->head.api_flag & tag)) { + vs.append(e->v1); + e->v1->head.api_flag |= tag; + } + + if (!(e->v2->head.api_flag & tag)) { + vs.append(e->v2); + e->v2->head.api_flag |= tag; + } + + e->head.api_flag |= tag; + } + + for (BMFace *f : fs) { + BMLoop *l = f->l_first; + + do { + if (!(l->v->head.api_flag & tag)) { + vs.append(l->v); + l->v->head.api_flag |= tag; + } + + if (!(l->e->head.api_flag & tag)) { + es.append(l->e); + l->e->head.api_flag |= tag; + } + } while ((l = l->next) != f->l_first); + } + + struct StackItem { + BMVert *v; + int depth; + }; + + Vector stack; + Set elemset; + + for (BMVert *v : vs) { + elemset.add(static_cast(v)); + } + for (BMEdge *e : es) { + elemset.add(static_cast(e)); + } + for (BMFace *f : fs) { + elemset.add(static_cast(f)); + } + + stack.clear(); + stack.append({vs[0], 0}); + while (stack.size() > 0) { + StackItem item = stack.pop_last(); + BMVert *v = item.v; + int startdepth = item.depth; + + if (elemset.add(static_cast(v))) { + vs.append(v); + } + + if (!v->e || item.depth > depth) { + continue; + } + + BMEdge *e = v->e; + do { + if (visit.add(static_cast(e))) { + stack.append({e->v1, startdepth + 1}); + stack.append({e->v2, startdepth + 1}); + } + + if (!e->l) { + continue; + } + + BMLoop *l = e->l; + do { + if (visit.add(static_cast(l->f))) { + if (elemset.add(static_cast(l->f))) { + fs.append(l->f); + } + + BMLoop *l2 = l; + do { + if (visit.add(static_cast(l->v))) { + stack.append({l->v, startdepth + 1}); + } + } while ((l2 = l2->next) != l); + } + } while ((l = l->radial_next) != e->l); + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + } + + char *str = buf; + int size = LOCAL_OBJ_SIZE - 1; + int stri = 0; + + char line[128]; + line[0] = 0; + + for (BMVert *v : vs) { + v->head.api_flag &= ~tag; + } + for (BMEdge *e : es) { + e->head.api_flag &= ~tag; + } + + for (BMFace *f : fs) { + f->head.api_flag &= ~tag; + } + for (BMVert *v : initial_vs) { + v->head.api_flag |= tag; + } + + for (BMEdge *e : initial_es) { + e->head.api_flag |= tag; + e->v1->head.api_flag |= tag; + e->v2->head.api_flag |= tag; + } + + for (BMFace *f : initial_fs) { + f->head.api_flag |= tag; + BMLoop *l = f->l_first; + + do { + l->v->head.api_flag |= tag; + } while ((l = l->next) != f->l_first); + } + + for (int i : vs.index_range()) { + BMVert *v = vs[i]; + + if (v->head.api_flag & tag) { + sprintf(line, "#select\n"); + str = obj_append_line(line, str, buf, &size, &stri); + } + + v->head.index = i + 1; + sprintf(line, "v %.4f %.4f %.4f\n", v->co[0], v->co[1], v->co[2]); + + str = obj_append_line(line, str, buf, &size, &stri); + } + + /* save wire edges */ + for (BMEdge *e : es) { + if (e->l) { + continue; + } + + sprintf(line, "l %d %d\n", e->v1->head.index, e->v2->head.index); + str = obj_append_line(line, str, buf, &size, &stri); + } + + for (BMFace *f : fs) { + BMLoop *l = f->l_first; + + sprintf(line, "f"); + str = obj_append_line(line, str, buf, &size, &stri); + + do { + sprintf(line, " %d", l->v->head.index); + + str = obj_append_line(line, str, buf, &size, &stri); + } while ((l = l->next) != f->l_first); + + str = obj_append_line("\n", str, buf, &size, &stri); + } + + return str; +} + +static void check_mesh_radial(BMesh *bm) +{ + return; + + BMIter iter; + BMEdge *e; + BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { + int count = 0; + BMLoop *l = e->l; + + if (!l) { + continue; + } + + do { + if (BM_elem_is_free((BMElem *)l, BM_LOOP)) { + printf("Freed loop in edge %p radial cycle\n", e); + } + + if (count++ > 100) { + printf("Corrupted radial cycle for %p\n", e); + break; + } + } while ((l = l->radial_next) != e->l); + } +} + +static void trigger_jvke_error(int err, char *obj_text) +{ + printf("========= ERROR %s============\n\n%s\n\n", bm_get_error_str(err), obj_text); +} + +int bmesh_elem_check_all(void *elem, char htype); + +extern char *_last_local_obj; + +# define JVKE_CHECK_ELEMENT(elem) \ + { \ + int err = 0; \ + if ((err = bmesh_elem_check_all(elem, (elem)->head.htype))) { \ + trigger_jvke_error(err, saved_obj); \ + } \ + } +#else +# define JVKE_CHECK_ELEMENT(elem) +#endif + +static bool cleanup_vert(BMesh *bm, BMVert *v, CollapseCallbacks *callbacks) +{ + BMEdge *e = v->e; + + if (!e->l || e->l->f == e->l->radial_next->f) { + return false; + } + + BMFace *f_example = nullptr; + + if (callbacks && callbacks->on_face_kill) { + BMIter iter; + BMFace *f; + BM_ITER_ELEM (f, &iter, v, BM_FACES_OF_VERT) { + callbacks->on_face_kill(callbacks->customdata, f); + } + } + + do { + BMLoop *l = e->l; + if (!l) { + continue; + } + + if (callbacks && callbacks->on_edge_kill) { + callbacks->on_edge_kill(callbacks->customdata, e); + } + + if (!f_example) { + f_example = l->f; + } + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + BMVert *v1 = BM_edge_other_vert(v->e, v); + BMVert *v2 = BM_edge_other_vert(BM_DISK_EDGE_NEXT(v->e, v), v); + BMVert *v3 = BM_edge_other_vert(BM_DISK_EDGE_NEXT(BM_DISK_EDGE_NEXT(v->e, v), v), v); + + BMFace *f = BM_face_create_quad_tri(bm, v1, v2, v3, nullptr, f_example, BM_CREATE_NOP); + BMLoop *l = f->l_first; + + if (callbacks && callbacks->on_vert_kill) { + callbacks->on_vert_kill(callbacks->customdata, v); + } + BM_vert_kill(bm, v); + + /* Ensure correct winding. */ + do { + if (l->radial_next != l && l->radial_next->v == l->v) { + BM_face_normal_flip(bm, f); + break; + } + } while ((l = l->next) != f->l_first); + + l = f->l_first; + do { + if (l != l->radial_next) { + BMLoop *l2 = l->radial_next; + if (l2->v != l->v) { + l2 = l2->next; + } + + CustomData_bmesh_copy_block(bm->ldata, l2->head.data, &l->head.data); + } + } while ((l = l->next) != f->l_first); + + if (callbacks && callbacks->on_face_create) { + callbacks->on_face_create(callbacks->customdata, f); + } + + return true; +} + +static void bmesh_kernel_check_val3_vert(BMesh *bm, BMEdge *e, CollapseCallbacks *callbacks) +{ + if (!e->l) { + return; + } + + bool stop; + + do { + stop = true; + + BMLoop *l = e->l; + + if (!l) { + break; + } + + do { + BMLoop *l2 = l->prev; + + if (l2 == l2->radial_next || !l2->v->e) { + continue; + } + + bool bad = false; + int count = 0; + + BMEdge *e2 = l2->v->e; + do { + if (!e2->l || e2->l == e2->l->radial_next || e2->l->radial_next->radial_next != e2->l) { + bad = true; + break; + } + + bad = bad || e2->l->f->len != 3 || e2->l->radial_next->f->len != 3; + count++; + } while ((e2 = BM_DISK_EDGE_NEXT(e2, l2->v)) != l2->v->e); + + bad = bad || count != 3; + + if (!bad) { + if (cleanup_vert(bm, l2->v, callbacks)) { + stop = false; + break; + } + } + } while ((l = l->radial_next) != e->l); + } while (!stop); +} + +/** + * \brief Join Vert Kill Edge (JVKE) + * + * Collapse an edge, merging surrounding data. + * + * Unlike #BM_vert_collapse_edge & #bmesh_kernel_join_edge_kill_vert + * which only handle 2 valence verts, + * this can handle any number of connected edges/faces. + * + *
+ * Before: -> After:
+ * +-+-+-+    +-+-+-+
+ * | | | |    | \ / |
+ * +-+-+-+    +--+--+
+ * | | | |    | / \ |
+ * +-+-+-+    +-+-+-+
+ * 
+ */ + +BMVert *join_vert_kill_edge( + BMesh *bm, BMEdge *e, BMVert *v_del, const bool do_del, CollapseCallbacks *callbacks) +{ + BMVert *v_conn = BM_edge_other_vert(e, v_del); + +#ifdef JVKE_DEBUG + char buf[LOCAL_OBJ_SIZE]; + + bool have_boundary = false; + + if (_last_local_obj) { + MEM_freeN(static_cast(_last_local_obj)); + } + + char *saved_obj = bm_save_local_obj_text(bm, 2, buf, "e", e); + + _last_local_obj = static_cast(MEM_mallocN(strlen(saved_obj) + 1, "_last_local_obj")); + BLI_strncpy(_last_local_obj, saved_obj, strlen(saved_obj) + 1); +#endif + + /* Destroy any valence-3 verts that might turn into non-manifold "fins." */ + bmesh_kernel_check_val3_vert(bm, e, callbacks); + + Set es; + Set fs; + + const int dup_tag = _FLAG_OVERLAP; + + if (callbacks && callbacks->on_vert_combine) { + callbacks->on_vert_combine(callbacks->customdata, v_conn, v_del); + } + + for (int i = 0; i < 2; i++) { + BMVert *v = i ? v_del : v_conn; + + BMEdge *e = v->e; + do { + es.add(e); + + BMLoop *l = e->l; + if (!l) { + continue; + } + + do { + fs.add(l->f); + BMLoop *l2 = l; + do { + es.add(l2->e); + BMLoop *l3 = l2; + do { + fs.add(l3->f); + } while ((l3 = l3->radial_next) != l2); + } while ((l2 = l2->next) != l); + } while ((l = l->radial_next) != e->l); + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + } + + /* Inform callbacks we've "killed" all the faces. */ + for (BMFace *f : fs) { + if (callbacks && callbacks->on_face_kill) { + callbacks->on_face_kill(callbacks->customdata, f); + } + } + + /* Unlink loops. */ + for (BMFace *f : fs) { + BMLoop *l = f->l_first; + do { + BMEdge *e = l->e; + bmesh_radial_loop_remove(l->e, l); + l->e = e; + } while ((l = l->next) != f->l_first); + } + + /* Swap edges. */ + for (BMEdge *e : es) { + if (e->v1 != v_del && e->v2 != v_del) { + continue; + } + + if (e->v1 == v_conn || e->v2 == v_conn) { + if (e->l) { + printf("%s: ErROR!\n", __func__); + } + + if (callbacks && callbacks->on_edge_kill) { + callbacks->on_edge_kill(callbacks->customdata, e); + } + BM_edge_kill(bm, e); + continue; + } + + BMVert *otherv = e->v1 == v_del ? e->v2 : e->v1; + + BMEdge *exist = BM_edge_exists(otherv, v_conn); + + if (exist) { + if (e->l) { + printf("%s: ERROR!\n", __func__); + } + + if (callbacks && callbacks->on_edge_combine) { + callbacks->on_edge_combine(callbacks->customdata, exist, e); + } + + /* Combine edge flags. The sharp flag is inverted + * so we can't just OR it. + */ + bool sharp = !(exist->head.hflag & BM_ELEM_SMOOTH) || !(e->head.hflag & BM_ELEM_SMOOTH); + + exist->head.hflag |= e->head.hflag; + + if (sharp) { + exist->head.hflag &= ~BM_ELEM_SMOOTH; + } + + if (callbacks && callbacks->on_edge_kill) { + callbacks->on_edge_kill(callbacks->customdata, e); + } + + BM_edge_kill(bm, e); + } + else { + if (callbacks->on_edge_kill) { + callbacks->on_edge_kill(callbacks->customdata, e); + } + bmesh_disk_vert_replace(e, v_conn, v_del); + if (callbacks && callbacks->on_edge_create) { + callbacks->on_edge_create(callbacks->customdata, e); + } + } + } + + auto remove_loop = [&bm](BMFace *f, BMLoop *l) { + l->next->prev = l->prev; + l->prev->next = l->next; + if (l == f->l_first) { + f->l_first = l->next; + } + + f->len--; + bm_kill_only_loop(bm, l); + }; + + /* Swap loops */ + for (BMFace *f : fs) { + BMLoop *l = f->l_first; + BMLoop *lnext; + bool found = false; + + /* Swap v_del and remove duplicate v_conn's. */ + do { + lnext = l->next; + + if (l->v == v_del) { + l->v = v_conn; + } + if (l->v == v_conn) { + if (found) { + remove_loop(f, l); + } + else { + found = true; + } + } + } while ((l = lnext) != f->l_first); + + /* Remove any remaining duplicate verts. */ + do { + lnext = l->next; + if (l->v == l->next->v) { + remove_loop(f, l); + } + } while ((l = lnext) != f->l_first); + } + + Vector finalfs; + + /* Relink faces. */ + for (BMFace *f : fs) { + if (f->len < 3) { + BMLoop *l = f->l_first; + BMLoop *lnext; + do { + lnext = l->next; + bm_kill_only_loop(bm, l); + } while ((l = lnext) != f->l_first); + + bm_kill_only_face(bm, f); + continue; + } + + BMLoop *l = f->l_first; + do { + BMEdge *exist_e = BM_edge_exists(l->v, l->next->v); + if (!exist_e) { + exist_e = BM_edge_create(bm, l->v, l->next->v, nullptr, BM_CREATE_NOP); + if (callbacks && callbacks->on_edge_create) { + callbacks->on_edge_create(callbacks->customdata, exist_e); + } + } + + l->e = exist_e; + bmesh_radial_loop_append(l->e, l); + + BM_ELEM_API_FLAG_DISABLE(l->v, dup_tag); + BM_ELEM_API_FLAG_DISABLE(l->e, dup_tag); + BM_ELEM_API_FLAG_DISABLE(l->f, dup_tag); + } while ((l = l->next) != f->l_first); + + if (callbacks && callbacks->on_face_create) { + callbacks->on_face_create(callbacks->customdata, f); + } + finalfs.append(f); + } + + for (BMFace *f : finalfs) { + if (BM_elem_is_free((BMElem *)f, BM_FACE)) { + continue; + } + + BMFace *f2; + while ((f2 = BM_face_find_double(f))) { + printf("%s: removing duplicate face.\n", __func__); + if (callbacks && callbacks->on_face_kill) { + callbacks->on_face_kill(callbacks->customdata, f2); + } + BM_face_kill(bm, f2); + } + } + + JVKE_CHECK_ELEMENT(v_conn); + + if (do_del && !v_del->e) { + if (callbacks && callbacks->on_vert_kill) { + callbacks->on_vert_kill(callbacks->customdata, v_del); + } + BM_vert_kill(bm, v_del); + } + + return v_conn; +} +} // namespace blender::bmesh + +namespace blender::bmesh { + +#ifdef JVKE_DEBUG +char *_last_local_obj = nullptr; +static ATTR_NO_OPT int bmesh_elem_check_all_intern(void *elem, char htype, int depth = 0) +{ + int ret = bmesh_elem_check(elem, htype); + + if (ret || depth > 2) { + return ret; + } + + return 0; +} + +int bmesh_elem_check_all(void *elem, char htype) +{ + return bmesh_elem_check_all_intern(elem, htype); +} +#endif +} // namespace blender::bmesh + +BMVert *bmesh_kernel_join_vert_kill_edge(BMesh *bm, BMEdge *e, BMVert *v_kill, const bool do_del) +{ + return blender::bmesh::join_vert_kill_edge(bm, e, v_kill, do_del, nullptr); +} diff --git a/source/blender/bmesh/intern/bmesh_collapse.hh b/source/blender/bmesh/intern/bmesh_collapse.hh new file mode 100644 index 00000000000..c6a4f903d2d --- /dev/null +++ b/source/blender/bmesh/intern/bmesh_collapse.hh @@ -0,0 +1,23 @@ +#include "BLI_function_ref.hh" + +struct BMVert; +struct BMEdge; +struct BMFace; +struct BMesh; + +namespace blender::bmesh { +struct CollapseCallbacks { + void *customdata; + void (*on_vert_kill)(void *customdata, BMVert *) = nullptr; + void (*on_edge_kill)(void *customdata, BMEdge *) = nullptr; + void (*on_face_kill)(void *customdata, BMFace *) = nullptr; + void (*on_vert_combine)(void *customdata, BMVert * /*dest*/, BMVert * /*source*/) = nullptr; + void (*on_edge_combine)(void *customdata, BMEdge * /*dest*/, BMEdge * /*source*/) = nullptr; + void (*on_vert_create)(void *customdata, BMVert *) = nullptr; + void (*on_edge_create)(void *customdata, BMEdge *) = nullptr; + void (*on_face_create)(void *customdata, BMFace *) = nullptr; +}; + +BMVert *join_vert_kill_edge( + BMesh *bm, BMEdge *e, BMVert *v_del, const bool do_del, CollapseCallbacks *callbacks); +} // namespace blender::bmesh diff --git a/source/blender/bmesh/intern/bmesh_core.cc b/source/blender/bmesh/intern/bmesh_core.cc index 1d5b116b2d6..257fed6f338 100644 --- a/source/blender/bmesh/intern/bmesh_core.cc +++ b/source/blender/bmesh/intern/bmesh_core.cc @@ -789,7 +789,7 @@ static void bm_kill_only_edge(BMesh *bm, BMEdge *e) * low level function, only frees the face, * doesn't change or adjust surrounding geometry */ -static void bm_kill_only_face(BMesh *bm, BMFace *f) +void bm_kill_only_face(BMesh *bm, BMFace *f) { if (bm->act_face == f) { bm->act_face = nullptr; @@ -816,7 +816,7 @@ static void bm_kill_only_face(BMesh *bm, BMFace *f) * low level function, only frees the loop, * doesn't change or adjust surrounding geometry */ -static void bm_kill_only_loop(BMesh *bm, BMLoop *l) +void bm_kill_only_loop(BMesh *bm, BMLoop *l) { bm->totloop--; bm->elem_index_dirty |= BM_LOOP; @@ -1824,89 +1824,6 @@ BMEdge *bmesh_kernel_join_edge_kill_vert(BMesh *bm, return nullptr; } -BMVert *bmesh_kernel_join_vert_kill_edge(BMesh *bm, - BMEdge *e_kill, - BMVert *v_kill, - const bool do_del, - const bool check_edge_exists, - const bool kill_degenerate_faces) -{ - BLI_SMALLSTACK_DECLARE(faces_degenerate, BMFace *); - BMVert *v_target = BM_edge_other_vert(e_kill, v_kill); - - BLI_assert(BM_vert_in_edge(e_kill, v_kill)); - - if (e_kill->l) { - BMLoop *l_kill, *l_first, *l_kill_next; - l_kill = l_first = e_kill->l; - do { - /* relink loops and fix vertex pointer */ - if (l_kill->next->v == v_kill) { - l_kill->next->v = v_target; - } - - l_kill->next->prev = l_kill->prev; - l_kill->prev->next = l_kill->next; - if (BM_FACE_FIRST_LOOP(l_kill->f) == l_kill) { - BM_FACE_FIRST_LOOP(l_kill->f) = l_kill->next; - } - - /* fix len attribute of face */ - l_kill->f->len--; - if (kill_degenerate_faces) { - if (l_kill->f->len < 3) { - BLI_SMALLSTACK_PUSH(faces_degenerate, l_kill->f); - } - } - l_kill_next = l_kill->radial_next; - - bm_kill_only_loop(bm, l_kill); - - } while ((l_kill = l_kill_next) != l_first); - - e_kill->l = nullptr; - } - - BM_edge_kill(bm, e_kill); - BM_CHECK_ELEMENT(v_kill); - BM_CHECK_ELEMENT(v_target); - - if (v_target->e && v_kill->e) { - /* Inline `BM_vert_splice(bm, v_target, v_kill)`. */ - BMEdge *e; - while ((e = v_kill->e)) { - BMEdge *e_target; - - if (check_edge_exists) { - e_target = BM_edge_exists(v_target, BM_edge_other_vert(e, v_kill)); - } - - bmesh_edge_vert_swap(e, v_target, v_kill); - BLI_assert(e->v1 != e->v2); - - if (check_edge_exists) { - if (e_target) { - BM_edge_splice(bm, e_target, e); - } - } - } - } - - if (kill_degenerate_faces) { - BMFace *f_kill; - while ((f_kill = static_cast(BLI_SMALLSTACK_POP(faces_degenerate)))) { - BM_face_kill(bm, f_kill); - } - } - - if (do_del) { - BLI_assert(v_kill->e == nullptr); - bm_kill_only_vert(bm, v_kill); - } - - return v_target; -} - BMFace *bmesh_kernel_join_face_kill_edge(BMesh *bm, BMFace *f1, BMFace *f2, BMEdge *e) { BMLoop *l_iter, *l_f1 = nullptr, *l_f2 = nullptr; diff --git a/source/blender/bmesh/intern/bmesh_core.hh b/source/blender/bmesh/intern/bmesh_core.hh index ceaf3be2cf7..26766268a2c 100644 --- a/source/blender/bmesh/intern/bmesh_core.hh +++ b/source/blender/bmesh/intern/bmesh_core.hh @@ -113,6 +113,7 @@ void BM_vert_kill(BMesh *bm, BMVert *v); * \note Edges must already have the same vertices. */ bool BM_edge_splice(BMesh *bm, BMEdge *e_dst, BMEdge *e_src); + /** * \brief Splice Vert * @@ -344,9 +345,8 @@ BMEdge *bmesh_kernel_join_edge_kill_vert(BMesh *bm, BMVert *bmesh_kernel_join_vert_kill_edge(BMesh *bm, BMEdge *e_kill, BMVert *v_kill, - bool do_del, - bool check_edge_exists, - bool kill_degenerate_faces); + const bool do_del); + /** * \brief Join Face Kill Edge (JFKE) * diff --git a/source/blender/bmesh/intern/bmesh_idmap.cc b/source/blender/bmesh/intern/bmesh_idmap.cc new file mode 100644 index 00000000000..77a97e01df6 --- /dev/null +++ b/source/blender/bmesh/intern/bmesh_idmap.cc @@ -0,0 +1,403 @@ +#include "MEM_guardedalloc.h" + +#include "BLI_assert.h" +#include "BLI_compiler_attrs.h" +#include "BLI_compiler_compat.h" +#include "BLI_index_range.hh" +#include "BLI_map.hh" +#include "BLI_set.hh" +#include "BLI_vector.hh" + +#include "BKE_customdata.hh" + +#include "DNA_customdata_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "bmesh_idmap.hh" +#include +#include + +using namespace blender; + +/* Threshold of size of BMIDMap.freelist where .free_idx_map + * (a hash map) will be created to find IDs inside the freelist. + */ +#define FREELIST_HASHMAP_THRESHOLD_HIGH 1024 +#define FREELIST_HASHMAP_THRESHOLD_LOW 700 + +const char *BM_idmap_attr_name_get(int htype) +{ + switch (htype) { + case BM_VERT: + return "vertex_id"; + case BM_EDGE: + return "edge_id"; + case BM_LOOP: + return "corner_id"; + case BM_FACE: + return "face_id"; + default: + BLI_assert_unreachable(); + return "error"; + } +} + +static void idmap_log_message(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); +} + +BMIdMap *BM_idmap_new(BMesh *bm, int elem_mask) +{ + BMIdMap *idmap = MEM_new("BMIdMap"); + + for (int i = 0; i < ARRAY_SIZE(idmap->cd_id_off); i++) { + idmap->cd_id_off[i] = -1; + } + + idmap->flag = elem_mask; + idmap->bm = bm; + idmap->maxid = BM_ID_NONE + 1; + + BM_idmap_check_attributes(idmap); + + return idmap; +} + +template static constexpr char get_elem_type() +{ + if constexpr (std::is_same_v) { + return BM_VERT; + } + else if constexpr (std::is_same_v) { + return BM_EDGE; + } + else if constexpr (std::is_same_v) { + return BM_LOOP; + } + else if constexpr (std::is_same_v) { + return BM_FACE; + } +} + +static void idmap_grow_map(BMIdMap *idmap, int newid) +{ + if (idmap->map.size() <= newid) { + idmap->map.resize(newid + 1); + } +} + +void BM_idmap_clear_attributes_mesh(Mesh *me) +{ + CustomData_free_layer_named(&me->vert_data, BM_idmap_attr_name_get(BM_VERT), me->verts_num); + CustomData_free_layer_named(&me->edge_data, BM_idmap_attr_name_get(BM_EDGE), me->edges_num); + CustomData_free_layer_named(&me->corner_data, BM_idmap_attr_name_get(BM_LOOP), me->corners_num); + CustomData_free_layer_named(&me->face_data, BM_idmap_attr_name_get(BM_FACE), me->faces_num); +} + +void BM_idmap_clear_attributes(BMesh *bm) +{ + BM_data_layer_free_named(bm, &bm->vdata, BM_idmap_attr_name_get(BM_VERT)); + BM_data_layer_free_named(bm, &bm->edata, BM_idmap_attr_name_get(BM_EDGE)); + BM_data_layer_free_named(bm, &bm->ldata, BM_idmap_attr_name_get(BM_LOOP)); + BM_data_layer_free_named(bm, &bm->pdata, BM_idmap_attr_name_get(BM_FACE)); +} + +void BM_idmap_check_ids(BMIdMap *idmap) +{ + BMIter iter; + BMVert *v; + BMEdge *e; + BMFace *f; + + BM_idmap_check_attributes(idmap); + + idmap->freelist.clear(); + if (idmap->free_idx_map) { + MEM_delete(idmap->free_idx_map); + idmap->free_idx_map = nullptr; + } + + int max_id = 1; + + if (idmap->flag & BM_VERT) { + BM_ITER_MESH (v, &iter, idmap->bm, BM_VERTS_OF_MESH) { + int id = BM_ELEM_CD_GET_INT(v, idmap->cd_id_off[BM_VERT]); + + max_id = max_ii(max_id, id); + } + } + if (idmap->flag & BM_EDGE) { + BM_ITER_MESH (e, &iter, idmap->bm, BM_EDGES_OF_MESH) { + int id = BM_ELEM_CD_GET_INT(e, idmap->cd_id_off[BM_EDGE]); + + max_id = max_ii(max_id, id); + } + } + if (idmap->flag & (BM_FACE | BM_LOOP)) { + BM_ITER_MESH (f, &iter, idmap->bm, BM_FACES_OF_MESH) { + if (idmap->flag & BM_FACE) { + int id = BM_ELEM_CD_GET_INT(f, idmap->cd_id_off[BM_FACE]); + max_id = max_ii(max_id, id); + } + + if (idmap->flag & BM_LOOP) { + BMLoop *l = f->l_first; + do { + int id = BM_ELEM_CD_GET_INT(l, idmap->cd_id_off[BM_LOOP]); + max_id = max_ii(max_id, id); + } while ((l = l->next) != f->l_first); + } + } + } + + max_id++; + + if (idmap->map.size() <= max_id) { + idmap->map.resize(max_id); + } + + /* Zero map. */ + memset(static_cast(idmap->map.data()), 0, sizeof(void *) * idmap->map.size()); + + auto check_elem = [&](auto *elem) { + int id = BM_ELEM_CD_GET_INT(elem, idmap->cd_id_off[int(elem->head.htype)]); + + if (id == BM_ID_NONE || id < 0 || (id < idmap->map.size() && idmap->map[id])) { + id = max_id++; + BM_ELEM_CD_SET_INT(elem, idmap->cd_id_off[int(elem->head.htype)], id); + } + + idmap_grow_map(idmap, id); + idmap->map[id] = reinterpret_cast(elem); + }; + + if (idmap->flag & BM_VERT) { + BM_ITER_MESH (v, &iter, idmap->bm, BM_VERTS_OF_MESH) { + check_elem(v); + } + } + if (idmap->flag & BM_EDGE) { + BM_ITER_MESH (e, &iter, idmap->bm, BM_EDGES_OF_MESH) { + check_elem(e); + } + } + if (idmap->flag & (BM_FACE | BM_LOOP)) { + BM_ITER_MESH (f, &iter, idmap->bm, BM_FACES_OF_MESH) { + check_elem(f); + if (idmap->flag & BM_LOOP) { + BMLoop *l = f->l_first; + + do { + check_elem(l); + } while ((l = l->next) != f->l_first); + } + } + } + + idmap->maxid = max_id + 1; +} + +static bool bm_idmap_check_attr(BMIdMap *idmap, int type) +{ + if (!(idmap->flag & type)) { + return false; + } + + CustomData *cdata; + const char *name = BM_idmap_attr_name_get(type); + + switch (type) { + case BM_VERT: + cdata = &idmap->bm->vdata; + break; + case BM_EDGE: + cdata = &idmap->bm->edata; + break; + case BM_LOOP: + cdata = &idmap->bm->ldata; + break; + case BM_FACE: + cdata = &idmap->bm->pdata; + break; + default: + BLI_assert_unreachable(); + return false; + } + + int idx = CustomData_get_named_layer_index(cdata, CD_PROP_INT32, name); + bool exists = idx != -1; + + if (!exists) { + BM_data_layer_add_named(idmap->bm, cdata, CD_PROP_INT32, name); + idx = CustomData_get_named_layer_index(cdata, CD_PROP_INT32, name); + } + + cdata->layers[idx].flag |= CD_FLAG_ELEM_NOINTERP | CD_FLAG_ELEM_NOCOPY; + idmap->cd_id_off[type] = cdata->layers[idx].offset; + + return !exists; +} + +bool BM_idmap_check_attributes(BMIdMap *idmap) +{ + bool ret = false; + + ret |= bm_idmap_check_attr(idmap, BM_VERT); + ret |= bm_idmap_check_attr(idmap, BM_EDGE); + ret |= bm_idmap_check_attr(idmap, BM_LOOP); + ret |= bm_idmap_check_attr(idmap, BM_FACE); + + return ret; +} + +void BM_idmap_destroy(BMIdMap *idmap) +{ + if (idmap->free_idx_map) { + MEM_delete(idmap->free_idx_map); + } + + MEM_delete(idmap); +} + +static void check_idx_map(BMIdMap *idmap) +{ + if (idmap->free_idx_map && idmap->freelist.size() < FREELIST_HASHMAP_THRESHOLD_LOW) { + MEM_delete(idmap->free_idx_map); + idmap->free_idx_map = nullptr; + } + else if (!idmap->free_idx_map && idmap->freelist.size() < FREELIST_HASHMAP_THRESHOLD_HIGH) { + idmap->free_idx_map = MEM_new("BMIdMap::FreeIdxMap"); + + for (int i : IndexRange(idmap->freelist.size())) { + idmap->free_idx_map->add(idmap->freelist[i], i); + } + } +} + +template int BM_idmap_alloc(BMIdMap *idmap, T *elem) +{ + int id = BM_ID_NONE; + + while (idmap->freelist.size()) { + id = idmap->freelist.pop_last(); + + if (id == BM_ID_NONE) { + continue; + } + + if (idmap->free_idx_map) { + idmap->free_idx_map->remove(id); + } + + break; + } + + if (id == BM_ID_NONE) { + id = idmap->maxid++; + } + + idmap_grow_map(idmap, id); + idmap->map[id] = reinterpret_cast(elem); + + BM_ELEM_CD_SET_INT(elem, idmap->cd_id_off[int(elem->head.htype)], id); + + return id; +} + +template void BM_idmap_assign(BMIdMap *idmap, T *elem, int id) +{ + /* Remove id from freelist. */ + if (idmap->free_idx_map) { + const int *val; + + if ((val = idmap->free_idx_map->lookup_ptr(id))) { + idmap->freelist[*val] = BM_ID_NONE; + idmap->free_idx_map->remove(id); + } + } + else { + for (int i : IndexRange(idmap->freelist.size())) { + if (idmap->freelist[i] == id) { + idmap->freelist[i] = BM_ID_NONE; + } + } + } + + BM_ELEM_CD_SET_INT(elem, idmap->cd_id_off[int(elem->head.htype)], id); + + idmap_grow_map(idmap, id); + idmap->map[id] = reinterpret_cast(elem); + + check_idx_map(idmap); +} + +template void BM_idmap_release(BMIdMap *idmap, T *elem, bool clear_id) +{ + int id = BM_ELEM_CD_GET_INT(elem, idmap->cd_id_off[int(elem->head.htype)]); + + if (id == BM_ID_NONE) { + idmap_log_message("%s: unassigned id!\n", __func__); + return; + }; + if (id < 0 || id >= idmap->map.size() || + (idmap->map[id] && idmap->map[id] != reinterpret_cast(elem))) + { + idmap_log_message("%s: id corruptions\n", __func__); + } + else { + idmap->map[id] = nullptr; + } + + idmap->freelist.append(id); + + if (idmap->free_idx_map) { + idmap->free_idx_map->add(id, idmap->freelist.size() - 1); + } + + check_idx_map(idmap); + + if (clear_id) { + BM_ELEM_CD_SET_INT(elem, idmap->cd_id_off[int(elem->head.htype)], BM_ID_NONE); + } +} + +template int BM_idmap_check_assign(BMIdMap *idmap, T *elem) +{ + int id = BM_ELEM_CD_GET_INT(elem, idmap->cd_id_off[int(elem->head.htype)]); + + if (id == BM_ID_NONE) { + id = BM_idmap_alloc(idmap, (BMElem *)elem); + } + + return id; +} + +/* Instantiate templates. */ +template void BM_idmap_assign(BMIdMap *idmap, BMElem *elem, int id); +template void BM_idmap_assign(BMIdMap *idmap, BMVert *elem, int id); +template void BM_idmap_assign(BMIdMap *idmap, BMEdge *elem, int id); +template void BM_idmap_assign(BMIdMap *idmap, BMLoop *elem, int id); +template void BM_idmap_assign(BMIdMap *idmap, BMFace *elem, int id); + +template int BM_idmap_check_assign(BMIdMap *idmap, BMElem *elem); +template int BM_idmap_check_assign(BMIdMap *idmap, BMVert *elem); +template int BM_idmap_check_assign(BMIdMap *idmap, BMEdge *elem); +template int BM_idmap_check_assign(BMIdMap *idmap, BMLoop *elem); +template int BM_idmap_check_assign(BMIdMap *idmap, BMFace *elem); + +template int BM_idmap_alloc(BMIdMap *idmap, BMElem *elem); +template int BM_idmap_alloc(BMIdMap *idmap, BMVert *elem); +template int BM_idmap_alloc(BMIdMap *idmap, BMEdge *elem); +template int BM_idmap_alloc(BMIdMap *idmap, BMLoop *elem); +template int BM_idmap_alloc(BMIdMap *idmap, BMFace *elem); + +template void BM_idmap_release(BMIdMap *idmap, BMElem *elem, bool clear_id); +template void BM_idmap_release(BMIdMap *idmap, BMVert *elem, bool clear_id); +template void BM_idmap_release(BMIdMap *idmap, BMEdge *elem, bool clear_id); +template void BM_idmap_release(BMIdMap *idmap, BMLoop *elem, bool clear_id); +template void BM_idmap_release(BMIdMap *idmap, BMFace *elem, bool clear_id); diff --git a/source/blender/bmesh/intern/bmesh_idmap.hh b/source/blender/bmesh/intern/bmesh_idmap.hh new file mode 100644 index 00000000000..a89e4ad4e53 --- /dev/null +++ b/source/blender/bmesh/intern/bmesh_idmap.hh @@ -0,0 +1,84 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_sys_types.h" + +/** \file + * \ingroup bmesh + * + * A simple self-contained element ID library. + * Stores IDs in integer attributes. + */ + +#include "BLI_compiler_compat.h" +#include "BLI_map.hh" +#include "BLI_sys_types.h" +#include "BLI_vector.hh" + +#include "bmesh.hh" + +#define BM_ID_NONE 0 + +struct BMIdMap { + int flag; + + uint maxid; + int cd_id_off[15]; + BMesh *bm; + + blender::Vector map; /* ID -> Element map. */ + blender::Vector freelist; + + using FreeIdxMap = blender::Map; + + /* maps ids to their position within the freelist + only used if freelist is bigger then a certain size, + see FREELIST_HASHMAP_THRESHOLD_HIGH in bmesh_construct.c.*/ + FreeIdxMap *free_idx_map; +}; + +BMIdMap *BM_idmap_new(BMesh *bm, int elem_mask); +void BM_idmap_destroy(BMIdMap *idmap); + +/* Ensures idmap attributes exist. */ +bool BM_idmap_check_attributes(BMIdMap *idmap); + +/* Ensures every element has a unique ID. */ +void BM_idmap_check_ids(BMIdMap *idmap); + +/* Explicitly assign an ID. id cannot be BM_ID_NONE (zero). */ +template void BM_idmap_assign(BMIdMap *idmap, T *elem, int id); + +/* Automatically allocate an ID. */ +template int BM_idmap_alloc(BMIdMap *idmap, T *elem); + +/* Checks if an element needs an ID (it's id is BM_ID_NONE), + * and if so allocates one. + */ +template int BM_idmap_check_assign(BMIdMap *idmap, T *elem); + +/* Release an ID; if clear_id is true the id attribute for + * that element will be set to BM_ID_NONE. + */ +template void BM_idmap_release(BMIdMap *idmap, T *elem, bool clear_id); + +const char *BM_idmap_attr_name_get(int htype); + +/* Deletes all id attributes. */ +void BM_idmap_clear_attributes(BMesh *bm); +void BM_idmap_clear_attributes_mesh(Mesh *me); + +/* Elem -> ID. */ +template BLI_INLINE int BM_idmap_get_id(BMIdMap *map, T *elem) +{ + return BM_ELEM_CD_GET_INT(elem, map->cd_id_off[(int)elem->head.htype]); +} + +/* ID -> elem. */ +template BLI_INLINE T *BM_idmap_lookup(BMIdMap *map, int elem) +{ + return elem >= 0 ? reinterpret_cast(map->map[elem]) : NULL; +} diff --git a/source/blender/bmesh/intern/bmesh_interp.cc b/source/blender/bmesh/intern/bmesh_interp.cc index e78c011d531..09abb5c8be9 100644 --- a/source/blender/bmesh/intern/bmesh_interp.cc +++ b/source/blender/bmesh/intern/bmesh_interp.cc @@ -19,6 +19,7 @@ #include "BLI_math_vector.h" #include "BLI_memarena.h" #include "BLI_task.h" +#include "BLI_vector.hh" #include "BKE_attribute.hh" #include "BKE_customdata.hh" @@ -27,6 +28,8 @@ #include "bmesh.hh" #include "intern/bmesh_private.hh" +using blender::Vector; + /* edge and vertex share, currently there's no need to have different logic */ static void bm_data_interp_from_elem(CustomData *data_layer, const BMElem *ele_src_1, @@ -773,6 +776,18 @@ static void update_data_blocks(BMesh *bm, CustomData *olddata, CustomData *data) BLI_mempool *oldpool = olddata->pool; void *block; + /* Temporarily clear CD_FLAG_ELEM_NOCOPY flags + * so we don't end up skipping them. + */ + + Vector nocopy_layers; + for (int i = 0; i < olddata->totlayer; i++) { + if (olddata->layers[i].flag & CD_FLAG_ELEM_NOCOPY) { + olddata->layers[i].flag &= ~CD_FLAG_ELEM_NOCOPY; + nocopy_layers.append(olddata->layers + i); + } + } + if (data == &bm->vdata) { BMVert *eve; @@ -829,6 +844,10 @@ static void update_data_blocks(BMesh *bm, CustomData *olddata, CustomData *data) BLI_assert(0); } + for (CustomDataLayer *layer : nocopy_layers) { + layer->flag |= CD_FLAG_ELEM_NOCOPY; + } + if (oldpool) { /* this should never happen but can when dissolve fails - #28960. */ BLI_assert(data->pool != oldpool); @@ -837,6 +856,54 @@ static void update_data_blocks(BMesh *bm, CustomData *olddata, CustomData *data) } } +void BM_data_layers_ensure(BMesh *bm, CustomData *data, BMCustomLayerReq *layers, int totlayer) +{ + bool modified = false; + CustomData old = *data; + + if (old.layers) { + old.layers = static_cast(MEM_dupallocN(old.layers)); + } + + for (int i = 0; i < totlayer; i++) { + BMCustomLayerReq *req = layers + i; + int idx; + + if (req->name) { + idx = CustomData_get_named_layer_index(data, eCustomDataType(req->type), req->name); + } + else { + idx = CustomData_get_layer_index(data, eCustomDataType(req->type)); + } + + if (idx < 0) { + modified = true; + + if (req->name) { + CustomData_add_layer_named(data, eCustomDataType(req->type), CD_SET_DEFAULT, 0, req->name); + idx = CustomData_get_named_layer_index(data, eCustomDataType(req->type), req->name); + } + else { + CustomData_add_layer(data, eCustomDataType(req->type), CD_SET_DEFAULT, 0); + idx = CustomData_get_layer_index(data, eCustomDataType(req->type)); + } + + data->layers[idx].flag |= req->flag; + } + } + + if (modified) { + /* the pool is now owned by olddata and must not be shared */ + data->pool = nullptr; + + update_data_blocks(bm, &old, data); + } + + if (old.layers) { + MEM_freeN(old.layers); + } +} + void BM_data_layer_add(BMesh *bm, CustomData *data, int type) { CustomData olddata = *data; diff --git a/source/blender/bmesh/intern/bmesh_interp.hh b/source/blender/bmesh/intern/bmesh_interp.hh index de26983cfb3..3f21756c982 100644 --- a/source/blender/bmesh/intern/bmesh_interp.hh +++ b/source/blender/bmesh/intern/bmesh_interp.hh @@ -58,6 +58,19 @@ void BM_data_interp_from_edges( */ void BM_data_interp_face_vert_edge( BMesh *bm, const BMVert *v_src_1, const BMVert *v_src_2, BMVert *v, BMEdge *e, float fac); + +typedef struct BMCustomLayerReq { + int type; + const char *name; /* Can be NULL. */ + int flag; +} BMCustomLayerReq; + +/* Add several layers at once to the bmesh. The advantage of + * this over BM_data_layer_add is that the customdata pools + * will only be reallocated once. + */ +void BM_data_layers_ensure(BMesh *bm, CustomData *data, BMCustomLayerReq *layers, int totlayer); + void BM_data_layer_add(BMesh *bm, CustomData *data, int type); void BM_data_layer_add_named(BMesh *bm, CustomData *data, int type, const char *name); void BM_data_layer_ensure_named(BMesh *bm, CustomData *data, int type, const char *name); diff --git a/source/blender/bmesh/intern/bmesh_log.cc b/source/blender/bmesh/intern/bmesh_log.cc index 855aa160cc6..d411cc51b68 100644 --- a/source/blender/bmesh/intern/bmesh_log.cc +++ b/source/blender/bmesh/intern/bmesh_log.cc @@ -5,1045 +5,1853 @@ /** \file * \ingroup bmesh * - * The BMLog is an interface for storing undo/redo steps as a BMesh is - * modified. It only stores changes to the BMesh, not full copies. - * - * Currently it supports the following types of changes: - * - * - Adding and removing vertices - * - Adding and removing faces - * - Moving vertices - * - Setting vertex paint-mask values - * - Setting vertex hflags */ #include "MEM_guardedalloc.h" -#include "BLI_ghash.h" -#include "BLI_listbase.h" -#include "BLI_math_vector.h" -#include "BLI_mempool.h" -#include "BLI_utildefines.h" - #include "BKE_customdata.hh" +#include "BKE_mesh.h" + +#include "DNA_customdata_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BLI_asan.h" +#include "BLI_hash.hh" +#include "BLI_listbase_wrapper.hh" +#include "BLI_map.hh" +#include "BLI_math_vector.h" +#include "BLI_math_vector_types.hh" +#include "BLI_mempool.h" +#include "BLI_set.hh" +#include "BLI_vector.hh" #include "bmesh.hh" +#include "bmesh_idmap.hh" #include "bmesh_log.hh" -#include "range_tree.h" -#include "BLI_strict_flags.h" /* Keep last. */ +#include +#include +#include +#include +#include +#include -struct BMLogEntry { - BMLogEntry *next, *prev; +using blender::float3; +using blender::Map; +using blender::Set; +using blender::Vector; - /* The following #GHash members map from an element ID to one of the log types above. */ +/* Avoid C++ runtime type ids. */ +enum BMLogSetType { LOG_SET_DIFF, LOG_SET_FULL }; - /** Elements that were in the previous entry, but have been deleted. */ - GHash *deleted_verts; - GHash *deleted_faces; - /** Elements that were not in the previous entry, but are in the result of this entry. */ - GHash *added_verts; - GHash *added_faces; +template struct BMID { + int id = -1; - /** Vertices whose coordinates, mask value, or hflag have changed. */ - GHash *modified_verts; - GHash *modified_faces; - - BLI_mempool *pool_verts; - BLI_mempool *pool_faces; - - /** - * This is only needed for dropping BMLogEntries while still in - * dynamic-topology mode, as that should release vert/face IDs - * back to the BMLog but no BMLog pointer is available at that time. - * - * This field is not guaranteed to be valid, any use of it should - * check for nullptr. - */ - BMLog *log; -}; - -struct BMLog { - /** Tree of free IDs */ - RangeTreeUInt *unused_ids; - - /** - * Mapping from unique IDs to vertices and faces - * - * Each vertex and face in the log gets a unique `uint` - * assigned. That ID is taken from the set managed by the - * unused_ids range tree. - * - * The ID is needed because element pointers will change as they - * are created and deleted. - */ - GHash *id_to_elem; - GHash *elem_to_id; - - /** All #BMLogEntrys, ordered from earliest to most recent. */ - ListBase entries; - - /** - * The current log entry from entries list - * - * If null, then the original mesh from before any of the log - * entries is current (i.e. there is nothing left to undo.) - * - * If equal to the last entry in the entries list, then all log - * entries have been applied (i.e. there is nothing left to redo.) - */ - BMLogEntry *current_entry; -}; - -struct BMLogVert { - float co[3]; - float no[3]; - char hflag; - float mask; -}; - -struct BMLogFace { - uint v_ids[3]; - char hflag; -}; - -/************************* Get/set element IDs ************************/ - -/* bypass actual hashing, the keys don't overlap */ -#define logkey_hash BLI_ghashutil_inthash_p_simple -#define logkey_cmp BLI_ghashutil_intcmp - -/* Get the vertex's unique ID from the log */ -static uint bm_log_vert_id_get(BMLog *log, BMVert *v) -{ - BLI_assert(BLI_ghash_haskey(log->elem_to_id, v)); - return POINTER_AS_UINT(BLI_ghash_lookup(log->elem_to_id, v)); -} - -/* Set the vertex's unique ID in the log */ -static void bm_log_vert_id_set(BMLog *log, BMVert *v, uint id) -{ - void *vid = POINTER_FROM_UINT(id); - - BLI_ghash_reinsert(log->id_to_elem, vid, v, nullptr, nullptr); - BLI_ghash_reinsert(log->elem_to_id, v, vid, nullptr, nullptr); -} - -/* Get a vertex from its unique ID */ -static BMVert *bm_log_vert_from_id(BMLog *log, uint id) -{ - void *key = POINTER_FROM_UINT(id); - BLI_assert(BLI_ghash_haskey(log->id_to_elem, key)); - return static_cast(BLI_ghash_lookup(log->id_to_elem, key)); -} - -/* Get the face's unique ID from the log */ -static uint bm_log_face_id_get(BMLog *log, BMFace *f) -{ - BLI_assert(BLI_ghash_haskey(log->elem_to_id, f)); - return POINTER_AS_UINT(BLI_ghash_lookup(log->elem_to_id, f)); -} - -/* Set the face's unique ID in the log */ -static void bm_log_face_id_set(BMLog *log, BMFace *f, uint id) -{ - void *fid = POINTER_FROM_UINT(id); - - BLI_ghash_reinsert(log->id_to_elem, fid, f, nullptr, nullptr); - BLI_ghash_reinsert(log->elem_to_id, f, fid, nullptr, nullptr); -} - -/* Get a face from its unique ID */ -static BMFace *bm_log_face_from_id(BMLog *log, uint id) -{ - void *key = POINTER_FROM_UINT(id); - BLI_assert(BLI_ghash_haskey(log->id_to_elem, key)); - return static_cast(BLI_ghash_lookup(log->id_to_elem, key)); -} - -/************************ BMLogVert / BMLogFace ***********************/ - -/* Get a vertex's paint-mask value - * - * Returns zero if no paint-mask layer is present */ -static float vert_mask_get(BMVert *v, const int cd_vert_mask_offset) -{ - if (cd_vert_mask_offset != -1) { - return BM_ELEM_CD_GET_FLOAT(v, cd_vert_mask_offset); - } - return 0.0f; -} - -/* Set a vertex's paint-mask value - * - * Has no effect is no paint-mask layer is present */ -static void vert_mask_set(BMVert *v, const float new_mask, const int cd_vert_mask_offset) -{ - if (cd_vert_mask_offset != -1) { - BM_ELEM_CD_SET_FLOAT(v, cd_vert_mask_offset, new_mask); - } -} - -/* Update a BMLogVert with data from a BMVert */ -static void bm_log_vert_bmvert_copy(BMLogVert *lv, BMVert *v, const int cd_vert_mask_offset) -{ - copy_v3_v3(lv->co, v->co); - copy_v3_v3(lv->no, v->no); - lv->mask = vert_mask_get(v, cd_vert_mask_offset); - lv->hflag = v->head.hflag; -} - -/* Allocate and initialize a BMLogVert */ -static BMLogVert *bm_log_vert_alloc(BMLog *log, BMVert *v, const int cd_vert_mask_offset) -{ - BMLogEntry *entry = log->current_entry; - BMLogVert *lv = static_cast(BLI_mempool_alloc(entry->pool_verts)); - - bm_log_vert_bmvert_copy(lv, v, cd_vert_mask_offset); - - return lv; -} - -/* Allocate and initialize a BMLogFace */ -static BMLogFace *bm_log_face_alloc(BMLog *log, BMFace *f) -{ - BMLogEntry *entry = log->current_entry; - BMLogFace *lf = static_cast(BLI_mempool_alloc(entry->pool_faces)); - BMVert *v[3]; - - BLI_assert(f->len == 3); - - // BM_iter_as_array(nullptr, BM_VERTS_OF_FACE, f, (void **)v, 3); - BM_face_as_array_vert_tri(f, v); - - lf->v_ids[0] = bm_log_vert_id_get(log, v[0]); - lf->v_ids[1] = bm_log_vert_id_get(log, v[1]); - lf->v_ids[2] = bm_log_vert_id_get(log, v[2]); - - lf->hflag = f->head.hflag; - return lf; -} - -/************************ Helpers for undo/redo ***********************/ - -static void bm_log_verts_unmake(BMesh *bm, BMLog *log, GHash *verts) -{ - const int cd_vert_mask_offset = CustomData_get_offset_named( - &bm->vdata, CD_PROP_FLOAT, ".sculpt_mask"); - - GHashIterator gh_iter; - GHASH_ITER (gh_iter, verts) { - void *key = BLI_ghashIterator_getKey(&gh_iter); - BMLogVert *lv = static_cast(BLI_ghashIterator_getValue(&gh_iter)); - uint id = POINTER_AS_UINT(key); - BMVert *v = bm_log_vert_from_id(log, id); - - /* Ensure the log has the final values of the vertex before - * deleting it */ - bm_log_vert_bmvert_copy(lv, v, cd_vert_mask_offset); - - BM_vert_kill(bm, v); - } -} - -static void bm_log_faces_unmake(BMesh *bm, BMLog *log, GHash *faces) -{ - GHashIterator gh_iter; - GHASH_ITER (gh_iter, faces) { - void *key = BLI_ghashIterator_getKey(&gh_iter); - uint id = POINTER_AS_UINT(key); - BMFace *f = bm_log_face_from_id(log, id); - BMEdge *e_tri[3]; - BMLoop *l_iter; - int i; - - l_iter = BM_FACE_FIRST_LOOP(f); - for (i = 0; i < 3; i++, l_iter = l_iter->next) { - e_tri[i] = l_iter->e; - } - - /* Remove any unused edges */ - BM_face_kill(bm, f); - for (i = 0; i < 3; i++) { - if (BM_edge_is_wire(e_tri[i])) { - BM_edge_kill(bm, e_tri[i]); - } - } - } -} - -static void bm_log_verts_restore(BMesh *bm, BMLog *log, GHash *verts) -{ - const int cd_vert_mask_offset = CustomData_get_offset_named( - &bm->vdata, CD_PROP_FLOAT, ".sculpt_mask"); - - GHashIterator gh_iter; - GHASH_ITER (gh_iter, verts) { - void *key = BLI_ghashIterator_getKey(&gh_iter); - BMLogVert *lv = static_cast(BLI_ghashIterator_getValue(&gh_iter)); - BMVert *v = BM_vert_create(bm, lv->co, nullptr, BM_CREATE_NOP); - vert_mask_set(v, lv->mask, cd_vert_mask_offset); - v->head.hflag = lv->hflag; - copy_v3_v3(v->no, lv->no); - bm_log_vert_id_set(log, v, POINTER_AS_UINT(key)); - } -} - -static void bm_log_faces_restore(BMesh *bm, BMLog *log, GHash *faces) -{ - GHashIterator gh_iter; - const int cd_face_sets = CustomData_get_offset_named( - &bm->pdata, CD_PROP_INT32, ".sculpt_face_set"); - - GHASH_ITER (gh_iter, faces) { - void *key = BLI_ghashIterator_getKey(&gh_iter); - BMLogFace *lf = static_cast(BLI_ghashIterator_getValue(&gh_iter)); - BMVert *v[3] = { - bm_log_vert_from_id(log, lf->v_ids[0]), - bm_log_vert_from_id(log, lf->v_ids[1]), - bm_log_vert_from_id(log, lf->v_ids[2]), - }; - BMFace *f; - - f = BM_face_create_verts(bm, v, 3, nullptr, BM_CREATE_NOP, true); - f->head.hflag = lf->hflag; - bm_log_face_id_set(log, f, POINTER_AS_UINT(key)); - - /* Ensure face sets have valid values. Fixes #80174. */ - if (cd_face_sets != -1) { - BM_ELEM_CD_SET_INT(f, cd_face_sets, 1); - } - } -} - -static void bm_log_vert_values_swap(BMesh *bm, BMLog *log, GHash *verts) -{ - const int cd_vert_mask_offset = CustomData_get_offset_named( - &bm->vdata, CD_PROP_FLOAT, ".sculpt_mask"); - - GHashIterator gh_iter; - GHASH_ITER (gh_iter, verts) { - void *key = BLI_ghashIterator_getKey(&gh_iter); - BMLogVert *lv = static_cast(BLI_ghashIterator_getValue(&gh_iter)); - uint id = POINTER_AS_UINT(key); - BMVert *v = bm_log_vert_from_id(log, id); - float mask; - - swap_v3_v3(v->co, lv->co); - swap_v3_v3(v->no, lv->no); - std::swap(v->head.hflag, lv->hflag); - mask = lv->mask; - lv->mask = vert_mask_get(v, cd_vert_mask_offset); - vert_mask_set(v, mask, cd_vert_mask_offset); - } -} - -static void bm_log_face_values_swap(BMLog *log, GHash *faces) -{ - GHashIterator gh_iter; - GHASH_ITER (gh_iter, faces) { - void *key = BLI_ghashIterator_getKey(&gh_iter); - BMLogFace *lf = static_cast(BLI_ghashIterator_getValue(&gh_iter)); - uint id = POINTER_AS_UINT(key); - BMFace *f = bm_log_face_from_id(log, id); - - std::swap(f->head.hflag, lf->hflag); - } -} - -/**********************************************************************/ - -/* Assign unique IDs to all vertices and faces already in the BMesh */ -static void bm_log_assign_ids(BMesh *bm, BMLog *log) -{ - BMIter iter; - BMVert *v; - BMFace *f; - - /* Generate vertex IDs */ - BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { - uint id = range_tree_uint_take_any(log->unused_ids); - bm_log_vert_id_set(log, v, id); - } - - /* Generate face IDs */ - BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { - uint id = range_tree_uint_take_any(log->unused_ids); - bm_log_face_id_set(log, f, id); - } -} - -/* Allocate an empty log entry */ -static BMLogEntry *bm_log_entry_create() -{ - BMLogEntry *entry = static_cast(MEM_callocN(sizeof(BMLogEntry), __func__)); - - entry->deleted_verts = BLI_ghash_new(logkey_hash, logkey_cmp, __func__); - entry->deleted_faces = BLI_ghash_new(logkey_hash, logkey_cmp, __func__); - entry->added_verts = BLI_ghash_new(logkey_hash, logkey_cmp, __func__); - entry->added_faces = BLI_ghash_new(logkey_hash, logkey_cmp, __func__); - entry->modified_verts = BLI_ghash_new(logkey_hash, logkey_cmp, __func__); - entry->modified_faces = BLI_ghash_new(logkey_hash, logkey_cmp, __func__); - - entry->pool_verts = BLI_mempool_create(sizeof(BMLogVert), 0, 64, BLI_MEMPOOL_NOP); - entry->pool_faces = BLI_mempool_create(sizeof(BMLogFace), 0, 64, BLI_MEMPOOL_NOP); - - return entry; -} - -/* Free the data in a log entry - * - * NOTE: does not free the log entry itself. */ -static void bm_log_entry_free(BMLogEntry *entry) -{ - BLI_ghash_free(entry->deleted_verts, nullptr, nullptr); - BLI_ghash_free(entry->deleted_faces, nullptr, nullptr); - BLI_ghash_free(entry->added_verts, nullptr, nullptr); - BLI_ghash_free(entry->added_faces, nullptr, nullptr); - BLI_ghash_free(entry->modified_verts, nullptr, nullptr); - BLI_ghash_free(entry->modified_faces, nullptr, nullptr); - - BLI_mempool_destroy(entry->pool_verts); - BLI_mempool_destroy(entry->pool_faces); -} - -static void bm_log_id_ghash_retake(RangeTreeUInt *unused_ids, GHash *id_ghash) -{ - GHashIterator gh_iter; - - GHASH_ITER (gh_iter, id_ghash) { - void *key = BLI_ghashIterator_getKey(&gh_iter); - uint id = POINTER_AS_UINT(key); - - range_tree_uint_retake(unused_ids, id); - } -} - -static int uint_compare(const void *a_v, const void *b_v) -{ - const uint *a = static_cast(a_v); - const uint *b = static_cast(b_v); - return (*a) < (*b); -} - -/* Remap IDs to contiguous indices - * - * E.g. if the vertex IDs are (4, 1, 10, 3), the mapping will be: - * 4 -> 2 - * 1 -> 0 - * 10 -> 3 - * 3 -> 1 - */ -static GHash *bm_log_compress_ids_to_indices(uint *ids, uint totid) -{ - GHash *map = BLI_ghash_int_new_ex(__func__, totid); - uint i; - - qsort(ids, totid, sizeof(*ids), uint_compare); - - for (i = 0; i < totid; i++) { - void *key = POINTER_FROM_UINT(ids[i]); - void *val = POINTER_FROM_UINT(i); - BLI_ghash_insert(map, key, val); - } - - return map; -} - -/* Release all ID keys in id_ghash */ -static void bm_log_id_ghash_release(BMLog *log, GHash *id_ghash) -{ - GHashIterator gh_iter; - - GHASH_ITER (gh_iter, id_ghash) { - void *key = BLI_ghashIterator_getKey(&gh_iter); - uint id = POINTER_AS_UINT(key); - range_tree_uint_release(log->unused_ids, id); - } -} - -/***************************** Public API *****************************/ - -BMLog *BM_log_create(BMesh *bm) -{ - BMLog *log = static_cast(MEM_callocN(sizeof(*log), __func__)); - const uint reserve_num = uint(bm->totvert + bm->totface); - - log->unused_ids = range_tree_uint_alloc(0, uint(-1)); - log->id_to_elem = BLI_ghash_new_ex(logkey_hash, logkey_cmp, __func__, reserve_num); - log->elem_to_id = BLI_ghash_ptr_new_ex(__func__, reserve_num); - - /* Assign IDs to all existing vertices and faces */ - bm_log_assign_ids(bm, log); - - return log; -} - -void BM_log_cleanup_entry(BMLogEntry *entry) -{ - BMLog *log = entry->log; - - if (log) { - /* Take all used IDs */ - bm_log_id_ghash_retake(log->unused_ids, entry->deleted_verts); - bm_log_id_ghash_retake(log->unused_ids, entry->deleted_faces); - bm_log_id_ghash_retake(log->unused_ids, entry->added_verts); - bm_log_id_ghash_retake(log->unused_ids, entry->added_faces); - bm_log_id_ghash_retake(log->unused_ids, entry->modified_verts); - bm_log_id_ghash_retake(log->unused_ids, entry->modified_faces); - - /* delete entries to avoid releasing ids in node cleanup */ - BLI_ghash_clear(entry->deleted_verts, nullptr, nullptr); - BLI_ghash_clear(entry->deleted_faces, nullptr, nullptr); - BLI_ghash_clear(entry->added_verts, nullptr, nullptr); - BLI_ghash_clear(entry->added_faces, nullptr, nullptr); - BLI_ghash_clear(entry->modified_verts, nullptr, nullptr); - } -} - -BMLog *BM_log_from_existing_entries_create(BMesh *bm, BMLogEntry *entry) -{ - BMLog *log = BM_log_create(bm); - - if (entry->prev) { - log->current_entry = entry; - } - else { - log->current_entry = nullptr; - } - - /* Let BMLog manage the entry list again */ - log->entries.first = log->entries.last = entry; + BMID(int _id) : id(_id) {} + T *lookup(BMIdMap *idmap) { - while (entry->prev) { - entry = entry->prev; - log->entries.first = entry; - } - entry = static_cast(log->entries.last); - while (entry->next) { - entry = entry->next; - log->entries.last = entry; - } + return BM_idmap_lookup(idmap, id); } - for (entry = static_cast(log->entries.first); entry; entry = entry->next) { - entry->log = log; - - /* Take all used IDs */ - bm_log_id_ghash_retake(log->unused_ids, entry->deleted_verts); - bm_log_id_ghash_retake(log->unused_ids, entry->deleted_faces); - bm_log_id_ghash_retake(log->unused_ids, entry->added_verts); - bm_log_id_ghash_retake(log->unused_ids, entry->added_faces); - bm_log_id_ghash_retake(log->unused_ids, entry->modified_verts); - bm_log_id_ghash_retake(log->unused_ids, entry->modified_faces); + uint64_t hash() const + { + return (uint64_t)id; } - return log; -} - -void BM_log_free(BMLog *log) -{ - if (log->unused_ids) { - range_tree_uint_free(log->unused_ids); + bool operator==(const BMID &b) const + { + return id == b.id; } +}; - if (log->id_to_elem) { - BLI_ghash_free(log->id_to_elem, nullptr, nullptr); - } +template struct BMLogElem { + BMID id = BMID(-1); + void *customdata = nullptr; + char flag = 0; - if (log->elem_to_id) { - BLI_ghash_free(log->elem_to_id, nullptr, nullptr); - } - - /* Clear the BMLog references within each entry, but do not free - * the entries themselves */ - LISTBASE_FOREACH (BMLogEntry *, entry, &log->entries) { - entry->log = nullptr; - } - - MEM_freeN(log); -} - -int BM_log_length(const BMLog *log) -{ - return BLI_listbase_count(&log->entries); -} - -void BM_log_mesh_elems_reorder(BMesh *bm, BMLog *log) -{ - uint *varr; - uint *farr; - - GHash *id_to_idx; - - BMIter bm_iter; - BMVert *v; - BMFace *f; - - uint i; - - /* Put all vertex IDs into an array */ - varr = static_cast(MEM_mallocN(sizeof(int) * size_t(bm->totvert), __func__)); - BM_ITER_MESH_INDEX (v, &bm_iter, bm, BM_VERTS_OF_MESH, i) { - varr[i] = bm_log_vert_id_get(log, v); - } - - /* Put all face IDs into an array */ - farr = static_cast(MEM_mallocN(sizeof(int) * size_t(bm->totface), __func__)); - BM_ITER_MESH_INDEX (f, &bm_iter, bm, BM_FACES_OF_MESH, i) { - farr[i] = bm_log_face_id_get(log, f); - } - - /* Create BMVert index remap array */ - id_to_idx = bm_log_compress_ids_to_indices(varr, uint(bm->totvert)); - BM_ITER_MESH_INDEX (v, &bm_iter, bm, BM_VERTS_OF_MESH, i) { - const uint id = bm_log_vert_id_get(log, v); - const void *key = POINTER_FROM_UINT(id); - const void *val = BLI_ghash_lookup(id_to_idx, key); - varr[i] = POINTER_AS_UINT(val); - } - BLI_ghash_free(id_to_idx, nullptr, nullptr); - - /* Create BMFace index remap array */ - id_to_idx = bm_log_compress_ids_to_indices(farr, uint(bm->totface)); - BM_ITER_MESH_INDEX (f, &bm_iter, bm, BM_FACES_OF_MESH, i) { - const uint id = bm_log_face_id_get(log, f); - const void *key = POINTER_FROM_UINT(id); - const void *val = BLI_ghash_lookup(id_to_idx, key); - farr[i] = POINTER_AS_UINT(val); - } - BLI_ghash_free(id_to_idx, nullptr, nullptr); - - BM_mesh_remap(bm, varr, nullptr, farr); - - MEM_freeN(varr); - MEM_freeN(farr); -} - -BMLogEntry *BM_log_entry_add(BMLog *log) -{ - /* WARNING: this is now handled by the UndoSystem: BKE_UNDOSYS_TYPE_SCULPT - * freeing here causes unnecessary complications. */ - BMLogEntry *entry; -#if 0 - /* Delete any entries after the current one */ - entry = log->current_entry; - if (entry) { - BMLogEntry *next; - for (entry = entry->next; entry; entry = next) { - next = entry->next; - bm_log_entry_free(entry); - BLI_freelinkN(&log->entries, entry); - } +#ifdef WITH_ASAN + bool dead = false; + ~BMLogElem() + { + dead = true; } #endif - /* Create and append the new entry */ - entry = bm_log_entry_create(); - BLI_addtail(&log->entries, entry); - entry->log = log; - log->current_entry = entry; + void free(CustomData *domain) + { + if (customdata) { + CustomData_bmesh_free_block_data(domain, customdata); + } + } +}; - return entry; +template struct LogElemAlloc { + BLI_mempool *pool; + + class iterator { + LogElemAlloc *alloc; + BLI_mempool_iter iter; + void *elem = nullptr, *first; + + public: + iterator(LogElemAlloc *_alloc) : alloc(_alloc) + { + BLI_mempool_iternew(_alloc->pool, &iter); + elem = first = BLI_mempool_iterstep(&iter); + } + + iterator(const iterator &b) : alloc(b.alloc), elem(b.elem), first(b.first) + { + iter = b.iter; + } + + iterator &operator++() + { + elem = BLI_mempool_iterstep(&iter); + + return *this; + } + + T &operator*() + { + return *reinterpret_cast(elem); + } + + iterator begin() + { + iterator start(*this); + start.elem = first; + return start; + } + + iterator end() + { + iterator end = iterator(*this); + end.elem = nullptr; + + return end; + } + + bool operator==(const iterator &b) + { + return elem == b.elem; + } + + bool operator!=(const iterator &b) + { + return elem != b.elem; + } + }; + + iterator elements() + { + return iterator(this); + } + + LogElemAlloc() + { + /* We need an iterable pool to call individual destructors in ~LogElemAlloc(). */ + pool = BLI_mempool_create(sizeof(T), 0, 256, BLI_MEMPOOL_ALLOW_ITER); + } + + LogElemAlloc(const LogElemAlloc &b) = delete; + + LogElemAlloc(LogElemAlloc &&b) + { + pool = b.pool; + b.pool = nullptr; + } + + int calc_size() + { + return int(BLI_mempool_get_size(pool)); + } + + void operator=(LogElemAlloc &&b) + { + pool = b.pool; + b.pool = nullptr; + } + + ~LogElemAlloc() + { + if (pool) { + BLI_mempool_iter iter; + BLI_mempool_iternew(pool, &iter); + while (void *entry = BLI_mempool_iterstep(&iter)) { + T *ptr = static_cast(entry); + ptr->~T(); + } + + BLI_mempool_destroy(pool); + } + } + + T *alloc() + { + void *mem = BLI_mempool_alloc(pool); + return new (mem) T(); + } + + void free(T *elem) + { + elem->~T(); + + BLI_mempool_free(pool, static_cast(elem)); + } +}; + +struct BMLogVert : public BMLogElem { + float3 co; + float3 no; +}; + +struct BMLogEdge : public BMLogElem { + BMID v1 = BMID(-1); + BMID v2 = BMID(-1); +}; + +struct BMLogFace : public BMLogElem { + Vector, 5> verts; + Vector loop_customdata; + + void free(CustomData *domain, CustomData *loop_domain) + { + BMLogElem::free(domain); + + if (loop_customdata[0]) { + for (void *data : loop_customdata) { + CustomData_bmesh_free_block_data(loop_domain, data); + } + } + } +}; + +struct BMLogEntry; + +static BMIdMap *entry_get_idmap(BMLogEntry *entry); + +struct BMLogSetBase { + BMLogSetType type; + BMLogEntry *entry = nullptr; /* Parent entry */ + + BMLogSetBase(BMLogEntry *_entry, BMLogSetType _type) : type(_type), entry(_entry) {} + + virtual ~BMLogSetBase() {} + + virtual const char *debug_name() + { + return ""; + } + virtual void undo(BMesh * /*bm*/, BMLogCallbacks * /*callbacks*/) {} + virtual void redo(BMesh * /*bm*/, BMLogCallbacks * /*callbacks*/) {} +}; + +struct BMLogSetDiff : public BMLogSetBase { + BMLogSetDiff(BMLogEntry *entry) : BMLogSetBase(entry, LOG_SET_DIFF) {} + + Map, BMLogVert *> modified_verts; + Map, BMLogEdge *> modified_edges; + Map, BMLogFace *> modified_faces; + + Map, BMLogVert *> removed_verts; + Map, BMLogEdge *> removed_edges; + Map, BMLogFace *> removed_faces; + + Map, BMLogVert *> added_verts; + Map, BMLogEdge *> added_edges; + Map, BMLogFace *> added_faces; + + const char *debug_name() override + { + return "Diff"; + } + + void add_vert(BMesh *bm, BMVert *v); + void remove_vert(BMesh *bm, BMVert *v); + void modify_vert(BMesh *bm, BMVert *v); + void add_edge(BMesh *bm, BMEdge *e); + void remove_edge(BMesh *bm, BMEdge *e); + void modify_edge(BMesh *bm, BMEdge *e); + void add_face(BMesh *bm, BMFace *f); + void remove_face(BMesh *bm, BMFace *f, bool no_check = false); + void modify_face(BMesh *bm, BMFace *f); + + void undo(BMesh *bm, BMLogCallbacks *callbacks) override; + void redo(BMesh *bm, BMLogCallbacks *callbacks) override; + + void restore_verts(BMesh *bm, + blender::Map, BMLogVert *> verts, + BMLogCallbacks *callbacks); + void remove_verts(BMesh *bm, + blender::Map, BMLogVert *> verts, + BMLogCallbacks *callbacks); + void swap_verts(BMesh *bm, + blender::Map, BMLogVert *> verts, + BMLogCallbacks *callbacks); + void restore_edges(BMesh *bm, + blender::Map, BMLogEdge *> edges, + BMLogCallbacks *callbacks); + void remove_edges(BMesh *bm, + blender::Map, BMLogEdge *> edges, + BMLogCallbacks *callbacks); + void swap_edges(BMesh *bm, + blender::Map, BMLogEdge *> edges, + BMLogCallbacks *callbacks); + + void restore_faces(BMesh *bm, + blender::Map, BMLogFace *> faces, + BMLogCallbacks *callbacks); + void remove_faces(BMesh *bm, + blender::Map, BMLogFace *> faces, + BMLogCallbacks *callbacks); + void swap_faces(BMesh *bm, + blender::Map, BMLogFace *> faces, + BMLogCallbacks *callbacks); +}; + +struct BMLogSetFull : public BMLogSetBase { + BMLogSetFull(BMesh *bm, BMLogEntry *entry) : BMLogSetBase(entry, LOG_SET_FULL) + { + BMeshToMeshParams params = {}; + params.update_shapekey_indices = false; + params.calc_object_remap = false; + params.copy_temp_cdlayers = true; + + mesh = BKE_mesh_from_bmesh_nomain(bm, ¶ms, nullptr); + } + + ~BMLogSetFull() + { + if (mesh) { + BKE_mesh_free_data_for_undo(mesh); + MEM_SAFE_FREE(mesh); + } + } + + const char *debug_name() override + { + return "Full"; + } + + void swap(BMesh *bm) + { + CustomData_MeshMasks cd_mask_extra = {0, 0, 0, 0, 0}; + + BMeshToMeshParams params = {}; + params.update_shapekey_indices = false; + params.calc_object_remap = false; + params.copy_temp_cdlayers = true; + + Mesh *current_mesh = BKE_mesh_from_bmesh_nomain(bm, ¶ms, nullptr); + + int shapenr = bm->shapenr; + BMeshFromMeshParams params2 = {}; + params2.copy_temp_cdlayers = true; + params2.cd_mask_extra = cd_mask_extra; + params2.calc_face_normal = params2.add_key_index = params2.use_shapekey = false; + + BM_mesh_clear(bm); + BM_mesh_bm_from_me(bm, + mesh, /* Note: we stored shapekeys as customdata layers, + * that's why the shapekey params are false. + */ + ¶ms2); + + /* Regenerate ID map. */ + BMIdMap *idmap = entry_get_idmap(entry); + BM_idmap_check_ids(idmap); + + bm->shapenr = shapenr; + + bm->elem_index_dirty |= BM_VERT | BM_EDGE | BM_FACE; + bm->elem_table_dirty |= BM_VERT | BM_EDGE | BM_FACE; + + BM_mesh_elem_table_ensure(bm, BM_VERT | BM_EDGE | BM_FACE); + BM_mesh_elem_index_ensure(bm, BM_VERT | BM_EDGE | BM_FACE); + + BKE_mesh_free_data_for_undo(mesh); + MEM_SAFE_FREE(mesh); + mesh = current_mesh; + } + + void undo(BMesh *bm, BMLogCallbacks *callbacks) override + { + swap(bm); + + if (callbacks && callbacks->on_full_mesh_load) { + callbacks->on_full_mesh_load(callbacks->userdata); + } + } + + void redo(BMesh *bm, BMLogCallbacks *callbacks) override + { + swap(bm); + if (callbacks && callbacks->on_full_mesh_load) { + callbacks->on_full_mesh_load(callbacks->userdata); + } + } + + Mesh *mesh = nullptr; +}; + +static const char *get_elem_htype_str(int htype) +{ + switch (htype) { + case BM_VERT: + return "vertex"; + case BM_EDGE: + return "edge"; + case BM_LOOP: + return "loop"; + case BM_FACE: + return "face"; + default: + return "unknown type"; + } } -void BM_log_entry_drop(BMLogEntry *entry) +template constexpr char get_elem_type() { - BMLog *log = entry->log; + if constexpr (std::is_same_v) { + return BM_VERT; + } + else if constexpr (std::is_same_v) { + return BM_EDGE; + } + else if constexpr (std::is_same_v) { + return BM_LOOP; + } + else if constexpr (std::is_same_v) { + return BM_FACE; + } +} - if (!log) { - /* Unlink */ - BLI_assert(!(entry->prev && entry->next)); - if (entry->prev) { - entry->prev->next = nullptr; - } - else if (entry->next) { - entry->next->prev = nullptr; +struct BMLogEntry { + BMLogEntry *next = nullptr, *prev = nullptr; + + Vector sets; + LogElemAlloc vpool; + LogElemAlloc epool; + LogElemAlloc fpool; + + /* Contains all faces from all differential subsets. */ + Set> verts; + Set> edges; + Set> faces; + + CustomData vdata, edata, ldata, pdata; + BMIdMap *idmap = nullptr; + + BMLog *log = nullptr; + bool dead = false; + + bool cd_layout_changed = false; + + BMLogEntry(BMIdMap *_idmap, + CustomData *src_vdata, + CustomData *src_edata, + CustomData *src_ldata, + CustomData *src_pdata) + : idmap(_idmap) + { +#if 1 + CustomData_copy_all_layout(src_vdata, &vdata); + CustomData_copy_all_layout(src_edata, &edata); + CustomData_copy_all_layout(src_ldata, &ldata); + CustomData_copy_all_layout(src_pdata, &pdata); +#else + vdata = *src_vdata; + edata = *src_edata; + ldata = *src_ldata; + pdata = *src_pdata; + + vdata.layers = static_cast( + MEM_dupallocN(static_cast(vdata.layers))); + edata.layers = static_cast( + MEM_dupallocN(static_cast(edata.layers))); + ldata.layers = static_cast( + MEM_dupallocN(static_cast(ldata.layers))); + pdata.layers = static_cast( + MEM_dupallocN(static_cast(pdata.layers))); + + vdata.pool = edata.pool = ldata.pool = pdata.pool = nullptr; +#endif + + CustomData_bmesh_init_pool(&vdata, 0, BM_VERT); + CustomData_bmesh_init_pool(&edata, 0, BM_EDGE); + CustomData_bmesh_init_pool(&ldata, 0, BM_LOOP); + CustomData_bmesh_init_pool(&pdata, 0, BM_FACE); + } + + void copy_custom_data(CustomData *source, CustomData *dest, void *src_block, void **dest_block) + { + if (!dest->pool) { + return; } - bm_log_entry_free(entry); - MEM_freeN(entry); + if (!*dest_block) { + *dest_block = BLI_mempool_calloc(dest->pool); + } + + CustomData_bmesh_copy_data(source, dest, src_block, dest_block); + } + + ~BMLogEntry() + { + dead = true; + + for (BMLogSetBase *set : sets) { + switch (set->type) { + case LOG_SET_DIFF: + delete static_cast(set); + break; + case LOG_SET_FULL: + delete static_cast(set); + break; + } + } + + for (BMLogVert &vert : vpool.elements()) { + vert.free(&vdata); + } + for (BMLogEdge &edge : epool.elements()) { + edge.free(&edata); + } + for (BMLogFace &face : fpool.elements()) { + face.free(&pdata, &ldata); + } + + if (vdata.pool) { + BLI_mempool_destroy(vdata.pool); + } + if (edata.pool) { + BLI_mempool_destroy(edata.pool); + } + if (ldata.pool) { + BLI_mempool_destroy(ldata.pool); + } + if (pdata.pool) { + BLI_mempool_destroy(pdata.pool); + } + + CustomData_free(&vdata, 0); + CustomData_free(&edata, 0); + CustomData_free(&ldata, 0); + CustomData_free(&pdata, 0); + } + + void print() + { + int av = 0, ae = 0, af = 0, mv = 0, me = 0, mf = 0, dv = 0, de = 0, df = 0; + int totmesh = 0; + + for (BMLogSetBase *set : sets) { + switch (set->type) { + case LOG_SET_DIFF: { + BMLogSetDiff *diff = static_cast(set); + + av += diff->added_verts.size(); + ae += diff->added_edges.size(); + af += diff->added_faces.size(); + + mv += diff->modified_verts.size(); + me += diff->modified_edges.size(); + mf += diff->modified_faces.size(); + + dv += diff->removed_verts.size(); + de += diff->removed_edges.size(); + df += diff->removed_faces.size(); + break; + } + case LOG_SET_FULL: + totmesh++; + break; + } + } + + if (av + ae + af + mv + me + mf + dv + de + df) { + printf(" addv: %d, adde: %d, addf: %d\n", av, ae, af); + printf(" modv: %d, mode: %d, modf: %d\n", mv, me, mf); + printf(" delv: %d, dele: %d, delf: %d\n", dv, de, df); + } + + if (totmesh > 0) { + printf(" totmesh: %d\n", totmesh); + } + } + + template T *get_elem_from_id(BMesh * /*bm*/, BMID id) + { + if (id.id < 0 || id.id >= idmap->map.size()) { + return nullptr; + } + + T *elem = BM_idmap_lookup(idmap, id.id); + char htype = 0; + + if (!elem) { + return nullptr; + } + + if constexpr (std::is_same_v) { + htype = BM_VERT; + } + if constexpr (std::is_same_v) { + htype = BM_EDGE; + } + if constexpr (std::is_same_v) { + htype = BM_FACE; + } + + if (elem->head.htype != htype) { + printf("%s: error: expected %s, got %s; id: %d\n", + __func__, + get_elem_htype_str(htype), + get_elem_htype_str(elem->head.htype), + id.id); + return nullptr; + } + + return elem; + } + + template void assign_elem_id(BMesh * /*bm*/, T *elem, BMID _id, bool check_unique) + { + int id = _id.id; + + if (check_unique && id >= 0 && id < idmap->map.size()) { + T *old = BM_idmap_lookup(idmap, id); + + if (old && old != elem) { + printf( + "id conflict in BMLogEntry::assign_elem_id; elem %p (a %s) is being reassinged to id " + "%d.\n", + elem, + get_elem_htype_str((int)elem->head.htype), + (int)id); + printf( + " elem %p (a %s) will get a new id\n", old, get_elem_htype_str((int)old->head.htype)); + + BM_idmap_assign(idmap, elem, id); + return; + } + } + + BM_idmap_assign(idmap, elem, id); + } + + template BMID get_elem_id(BMesh * /*bm*/, T *elem) + { + BM_idmap_check_assign(idmap, elem); + return BM_idmap_get_id(idmap, elem); + } + + void push_set(BMesh *bm, BMLogSetType type) + { + switch (type) { + case LOG_SET_DIFF: + sets.append(static_cast(new BMLogSetDiff(this))); + break; + case LOG_SET_FULL: + sets.append(static_cast(new BMLogSetFull(bm, this))); + break; + } + } + + BMLogSetDiff *current_diff_set(BMesh *bm) + { + if (sets.size() == 0 || sets[sets.size() - 1]->type != LOG_SET_DIFF) { + push_set(bm, LOG_SET_DIFF); + } + + return static_cast(sets[sets.size() - 1]); + } + + BMLogSetDiff *first_diff_set(BMesh *bm) + { + for (BMLogSetBase *set : sets) { + if (set->type == LOG_SET_DIFF) { + return static_cast(set); + } + } + + return current_diff_set(bm); + } + + void update_logvert(BMesh *bm, BMVert *v, BMLogVert *lv) + { + copy_custom_data(&bm->vdata, &vdata, v->head.data, &lv->customdata); + + lv->co = v->co; + lv->no = v->no; + lv->flag = v->head.hflag; + } + + void swap_logvert(BMesh *bm, BMID /*id*/, BMVert *v, BMLogVert *lv) + { + if (v->head.data && lv->customdata) { + CustomData_bmesh_swap_data(&vdata, &bm->vdata, lv->customdata, &v->head.data); + } + + std::swap(v->head.hflag, lv->flag); + swap_v3_v3(v->co, lv->co); + swap_v3_v3(v->no, lv->no); + } + + void swap_logedge(BMesh *bm, BMID /*id*/, BMEdge *e, BMLogEdge *le) + { + if (e->head.data && le->customdata) { + CustomData_bmesh_swap_data(&edata, &bm->edata, le->customdata, &e->head.data); + } + + std::swap(e->head.hflag, le->flag); + } + + void swap_logface(BMesh *bm, BMID /*id*/, BMFace *f, BMLogFace *lf) + { + if (f->head.data && lf->customdata) { + CustomData_bmesh_swap_data(&pdata, &bm->pdata, lf->customdata, &f->head.data); + } + + if (f->len != lf->verts.size()) { + printf("%s: error: wrong length for face, was %d, should be %d\n", + __func__, + f->len, + (int)lf->verts.size()); + return; + } + + if (lf->loop_customdata[0]) { + BMLoop *l = f->l_first; + + int i = 0; + do { + CustomData_bmesh_swap_data(&ldata, &bm->ldata, lf->loop_customdata[i], &l->head.data); + i++; + } while ((l = l->next) != f->l_first); + } + std::swap(f->head.hflag, lf->flag); + } + + BMLogVert *alloc_logvert(BMesh *bm, BMVert *v) + { + BMID id = get_elem_id(bm, v); + BMLogVert *lv = vpool.alloc(); + + lv->id = id; + + update_logvert(bm, v, lv); + + return lv; + } + + void free_logvert(BMLogVert *lv) + { + if (lv->customdata) { + BLI_mempool_free(vdata.pool, lv->customdata); + } + + vpool.free(lv); + } + + void load_vert(BMesh *bm, BMVert *v, BMLogVert *lv) + { + if (v->head.data && lv->customdata) { + CustomData_bmesh_copy_data(&vdata, &bm->vdata, lv->customdata, &v->head.data); + } + + v->head.hflag = lv->flag; + copy_v3_v3(v->co, lv->co); + copy_v3_v3(v->no, lv->no); + } + + BMLogEdge *alloc_logedge(BMesh *bm, BMEdge *e) + { + BMLogEdge *le = epool.alloc(); + + le->id = get_elem_id(bm, e); + le->v1 = get_elem_id(bm, e->v1); + le->v2 = get_elem_id(bm, e->v2); + + edges.add(le->id); + + update_logedge(bm, e, le); + + return le; + } + + void update_logedge(BMesh *bm, BMEdge *e, BMLogEdge *le) + { + le->flag = e->head.hflag; + copy_custom_data(&bm->edata, &edata, e->head.data, &le->customdata); + } + + void free_logedge(BMesh * /*bm*/, BMLogEdge *le) + { + if (le->customdata) { + BLI_mempool_free(edata.pool, le->customdata); + } + + epool.free(le); + } + + BMLogFace *alloc_logface(BMesh *bm, BMFace *f) + { + BMLogFace *lf = fpool.alloc(); + + lf->id = get_elem_id(bm, f); + lf->flag = f->head.hflag; + + copy_custom_data(&bm->pdata, &pdata, f->head.data, &lf->customdata); + + BMLoop *l = f->l_first; + do { + lf->verts.append(get_elem_id(bm, l->v)); + void *loop_customdata = nullptr; + + if (l->head.data) { + copy_custom_data(&bm->ldata, &ldata, l->head.data, &loop_customdata); + } + + lf->loop_customdata.append(loop_customdata); + } while ((l = l->next) != f->l_first); + + return lf; + } + + void update_logface(BMesh *bm, BMLogFace *lf, BMFace *f) + { + lf->flag = f->head.hflag; + + copy_custom_data(&bm->pdata, &pdata, f->head.data, &lf->customdata); + + if (f->len != lf->verts.size()) { + printf("%s: error: face length changed.\n", __func__); + return; + } + + BMLoop *l = f->l_first; + int i = 0; + do { + if (l->head.data) { + copy_custom_data(&bm->ldata, &ldata, l->head.data, &lf->loop_customdata[i]); + } + + i++; + } while ((l = l->next) != f->l_first); + } + + void free_logface(BMesh * /*bm*/, BMLogFace *lf) + { + if (lf->loop_customdata[0]) { + for (int i = 0; i < lf->verts.size(); i++) { + BLI_mempool_free(ldata.pool, lf->loop_customdata[i]); + } + } + + if (lf->customdata) { + BLI_mempool_free(pdata.pool, lf->customdata); + } + + fpool.free(lf); + } + + void add_vert(BMesh *bm, BMVert *v) + { + current_diff_set(bm)->add_vert(bm, v); + } + + void remove_vert(BMesh *bm, BMVert *v) + { + current_diff_set(bm)->remove_vert(bm, v); + } + + void modify_vert(BMesh *bm, BMVert *v) + { + current_diff_set(bm)->modify_vert(bm, v); + } + + void modify_if_vert(BMesh *bm, BMVert *v) + { + BMID id = get_elem_id(bm, v); + + if (!verts.contains(id)) { + current_diff_set(bm)->modify_vert(bm, v); + } + } + + void add_edge(BMesh *bm, BMEdge *e) + { + current_diff_set(bm)->add_edge(bm, e); + } + + void remove_edge(BMesh *bm, BMEdge *e) + { + current_diff_set(bm)->remove_edge(bm, e); + } + + void modify_edge(BMesh *bm, BMEdge *e) + { + current_diff_set(bm)->modify_edge(bm, e); + } + + void add_face(BMesh *bm, BMFace *f) + { + current_diff_set(bm)->add_face(bm, f); + } + void remove_face(BMesh *bm, BMFace *f, bool no_check = false) + { + current_diff_set(bm)->remove_face(bm, f, no_check); + } + void modify_face(BMesh *bm, BMFace *f) + { + current_diff_set(bm)->modify_face(bm, f); + } + void modify_if_face(BMesh *bm, BMFace *f) + { + BMID id = get_elem_id(bm, f); + + if (!faces.contains(id)) { + current_diff_set(bm)->modify_face(bm, f); + } + } + + void undo(BMesh *bm, BMLogCallbacks *callbacks) + { + for (int i = sets.size() - 1; i >= 0; i--) { + sets[i]->undo(bm, callbacks); + } + } + + void redo(BMesh *bm, BMLogCallbacks *callbacks) + { + for (int i = 0; i < sets.size(); i++) { + sets[i]->redo(bm, callbacks); + } + } + + int calc_size() + { + int ret = 0; + + ret += vdata.pool ? int(BLI_mempool_get_size(vdata.pool)) : 0; + ret += edata.pool ? int(BLI_mempool_get_size(edata.pool)) : 0; + ret += ldata.pool ? int(BLI_mempool_get_size(ldata.pool)) : 0; + ret += pdata.pool ? int(BLI_mempool_get_size(pdata.pool)) : 0; + + ret += vpool.calc_size(); + ret += epool.calc_size(); + ret += fpool.calc_size(); + + return ret; + } +}; + +struct BMLog { + BMIdMap *idmap = nullptr; + BMLogEntry *current_entry = nullptr; + BMLogEntry *first_entry = nullptr; + int refcount = 1; + bool dead = false; + + BMLog(BMIdMap *_idmap) : idmap(_idmap) {} + + ~BMLog() {} + + void set_idmap(BMIdMap *new_idmap) + { + idmap = new_idmap; + + BMLogEntry *entry = first_entry; + while (entry) { + entry->idmap = new_idmap; + entry = entry->next; + } + } + + bool free_all_entries() + { + printf("Freeing all log entries.\n"); + + BMLogEntry *entry = first_entry; + + if (!entry) { + return false; + } + + while (entry) { + BMLogEntry *next = entry->next; + + MEM_delete(entry); + entry = next; + } + + return true; + } + + BMLogEntry *push_entry(BMesh *bm) + { + BMLogEntry *entry = MEM_new( + "BMLogEntry", idmap, &bm->vdata, &bm->edata, &bm->ldata, &bm->pdata); + + /* Truncate undo list. */ + BMLogEntry *entry2 = current_entry ? current_entry->next : nullptr; + while (entry2) { + BMLogEntry *next = entry2->next; + MEM_delete(entry2); + + entry2 = next; + } + + entry->prev = current_entry; + entry->log = this; + entry->idmap = idmap; + + if (!first_entry) { + first_entry = entry; + } + else if (current_entry) { + current_entry->next = entry; + } + + current_entry = entry; + return entry; + } + + void load_entries(BMLogEntry *entry) + { + first_entry = current_entry = entry; + while (first_entry->prev) { + first_entry = first_entry->prev; + } + + entry = first_entry; + while (entry) { + entry->log = this; + entry->idmap = idmap; + entry = entry->next; + } + } + + void ensure_entry(BMesh *bm) + { + if (!current_entry) { + push_entry(bm); + } + } + + void add_vert(BMesh *bm, BMVert *v) + { + ensure_entry(bm); + current_entry->add_vert(bm, v); + } + + void remove_vert(BMesh *bm, BMVert *v) + { + ensure_entry(bm); + current_entry->remove_vert(bm, v); + } + + void modify_vert(BMesh *bm, BMVert *v) + { + ensure_entry(bm); + current_entry->modify_vert(bm, v); + } + + void modify_if_vert(BMesh *bm, BMVert *v) + { + ensure_entry(bm); + current_entry->modify_if_vert(bm, v); + } + + void add_edge(BMesh *bm, BMEdge *e) + { + ensure_entry(bm); + current_entry->add_edge(bm, e); + } + + void remove_edge(BMesh *bm, BMEdge *e) + { + ensure_entry(bm); + current_entry->remove_edge(bm, e); + } + + void modify_edge(BMesh *bm, BMEdge *e) + { + ensure_entry(bm); + current_entry->modify_edge(bm, e); + } + + void add_face(BMesh *bm, BMFace *f) + { + ensure_entry(bm); + current_entry->add_face(bm, f); + } + + void remove_face(BMesh *bm, BMFace *f, bool no_check = false) + { + ensure_entry(bm); + current_entry->remove_face(bm, f, no_check); + } + + void modify_face(BMesh *bm, BMFace *f) + { + ensure_entry(bm); + current_entry->modify_face(bm, f); + } + + void modify_if_face(BMesh *bm, BMFace *f) + { + ensure_entry(bm); + current_entry->modify_if_face(bm, f); + } + + void full_mesh(BMesh *bm) + { + ensure_entry(bm); + current_entry->push_set(bm, LOG_SET_FULL); + } + + void skip(int dir) + { + if (current_entry) { + current_entry = dir > 0 ? current_entry->next : current_entry->prev; + } + } + + void undo(BMesh *bm, BMLogCallbacks *callbacks) + { + if (!current_entry) { + current_entry = first_entry; + while (current_entry->next) { + current_entry = current_entry->next; + } + } + + current_entry->undo(bm, callbacks); + current_entry = current_entry->prev; + } + + void redo(BMesh *bm, BMLogCallbacks *callbacks) + { + if (!current_entry) { + current_entry = first_entry; + } + + current_entry = current_entry->next; + if (current_entry) { + current_entry->redo(bm, callbacks); + } + } +}; + +void BMLogSetDiff::add_vert(BMesh *bm, BMVert *v) +{ + BMID id = entry->get_elem_id(bm, v); + + BMLogVert *lv = nullptr; + if (added_verts.contains(id)) { return; } - if (!entry->prev) { - /* Release IDs of elements that are deleted by this - * entry. Since the entry is at the beginning of the undo - * stack, and it's being deleted, those elements can never be - * restored. Their IDs can go back into the pool. */ - - /* This would never happen usually since first entry of log is - * usually dyntopo enable, which, when reverted will free the log - * completely. However, it is possible have a stroke instead of - * dyntopo enable as first entry if nodes have been cleaned up - * after sculpting on a different object than A, B. - * - * The steps are: - * A dyntopo enable - sculpt - * B dyntopo enable - sculpt - undo (A objects operators get cleaned up) - * A sculpt (now A's log has a sculpt operator as first entry) - * - * Causing a cleanup at this point will call the code below, however - * this will invalidate the state of the log since the deleted vertices - * have been reclaimed already on step 2 (see BM_log_cleanup_entry) - * - * Also, design wise, a first entry should not have any deleted vertices since it - * should not have anything to delete them -from- - */ - // bm_log_id_ghash_release(log, entry->deleted_faces); - // bm_log_id_ghash_release(log, entry->deleted_verts); + if (!lv) { + lv = entry->alloc_logvert(bm, v); } - else if (!entry->next) { - /* Release IDs of elements that are added by this entry. Since - * the entry is at the end of the undo stack, and it's being - * deleted, those elements can never be restored. Their IDs - * can go back into the pool. */ - bm_log_id_ghash_release(log, entry->added_faces); - bm_log_id_ghash_release(log, entry->added_verts); + + added_verts.add(id, lv); +} + +void BMLogSetDiff::remove_vert(BMesh *bm, BMVert *v) +{ + BMID id = entry->get_elem_id(bm, v); + + BMLogVert **added_lv = added_verts.lookup_ptr(id); + if (added_lv) { + added_verts.remove(id); + entry->free_logvert(*added_lv); + return; + } + + if (removed_verts.contains(id)) { + return; + } + + BMLogVert *lv; + BMLogVert **modified_lv = modified_verts.lookup_ptr(id); + if (modified_lv) { + modified_verts.remove(id); + lv = *modified_lv; } else { - BLI_assert_msg(0, "Cannot drop BMLogEntry from middle"); + lv = entry->alloc_logvert(bm, v); } - if (log->current_entry == entry) { - log->current_entry = entry->prev; - } - - bm_log_entry_free(entry); - BLI_freelinkN(&log->entries, entry); + removed_verts.add(id, lv); } -void BM_log_undo(BMesh *bm, BMLog *log) +void BMLogSetDiff::modify_vert(BMesh *bm, BMVert *v) { - BMLogEntry *entry = log->current_entry; + BMID id = entry->get_elem_id(bm, v); + if (modified_verts.contains(id)) { + return; + } - if (entry) { - log->current_entry = entry->prev; + entry->verts.add(id); + modified_verts.add(id, entry->alloc_logvert(bm, v)); +} - /* Delete added faces and verts */ - bm_log_faces_unmake(bm, log, entry->added_faces); - bm_log_verts_unmake(bm, log, entry->added_verts); +void BMLogSetDiff::add_edge(BMesh *bm, BMEdge *e) +{ + BMID id = entry->get_elem_id(bm, e); + BMLogEdge *le; - /* Restore deleted verts and faces */ - bm_log_verts_restore(bm, log, entry->deleted_verts); - bm_log_faces_restore(bm, log, entry->deleted_faces); + le = entry->alloc_logedge(bm, e); + added_edges.add_or_modify( + id, [&](BMLogEdge **le_out) { *le_out = le; }, [&](BMLogEdge **le_out) { *le_out = le; }); +} - /* Restore vertex coordinates, mask, and hflag */ - bm_log_vert_values_swap(bm, log, entry->modified_verts); - bm_log_face_values_swap(log, entry->modified_faces); +void BMLogSetDiff::remove_edge(BMesh *bm, BMEdge *e) +{ + BMID id = entry->get_elem_id(bm, e); + + if (added_edges.remove(id) || removed_edges.contains(id)) { + return; + } + + BMLogEdge *le; + BMLogEdge **modified_le = modified_edges.lookup_ptr(id); + if (modified_le) { + le = *modified_le; + modified_edges.remove(id); + } + else { + le = entry->alloc_logedge(bm, e); + } + + removed_edges.add(id, le); +} + +void BMLogSetDiff::modify_edge(BMesh *bm, BMEdge *e) +{ + BMID id = entry->get_elem_id(bm, e); + + if (modified_edges.contains(id)) { + return; + } + + modified_edges.add(id, entry->alloc_logedge(bm, e)); +} + +void BMLogSetDiff::add_face(BMesh *bm, BMFace *f) +{ + BM_idmap_check_assign(entry->idmap, f); + + BMID id = entry->get_elem_id(bm, f); + + if (added_faces.contains(id)) { + return; + } + + added_faces.add(id, entry->alloc_logface(bm, f)); +} + +void BMLogSetDiff::remove_face(BMesh *bm, BMFace *f, bool no_check) +{ + BMID id = entry->get_elem_id(bm, f); + + if (!no_check && (added_faces.remove(id) || removed_faces.contains(id))) { + return; + } + + BMLogFace *lf; + + if (BMLogFace **ptr = modified_faces.lookup_ptr(id)) { + lf = *ptr; + modified_faces.remove(id); + if (lf->verts.size() != f->len) { + entry->update_logface(bm, lf, f); + } + } + else { + lf = entry->alloc_logface(bm, f); + } + + removed_faces.add(id, lf); +} + +void BMLogSetDiff::modify_face(BMesh *bm, BMFace *f) +{ + BMID id = entry->get_elem_id(bm, f); + + BMLogFace *lf; + + if (BMLogFace **ptr = modified_faces.lookup_ptr(id)) { + lf = *ptr; + entry->update_logface(bm, lf, f); + } + else { + lf = entry->alloc_logface(bm, f); + + modified_faces.add(id, lf); + entry->faces.add(lf->id); } } -void BM_log_redo(BMesh *bm, BMLog *log) +void BMLogSetDiff::swap_verts(BMesh *bm, + blender::Map, BMLogVert *> verts, + BMLogCallbacks *callbacks) +{ + void *old_customdata = bm->vdata.pool ? BLI_mempool_alloc(bm->vdata.pool) : nullptr; + + const int cd_id = entry->idmap->cd_id_off[BM_VERT]; + + for (BMLogVert *lv : verts.values()) { + BMVert *v = entry->get_elem_from_id(bm, lv->id); + + if (!v) { + printf("modified_verts: invalid vertex %d\n", lv->id.id); + continue; + } + + if (old_customdata) { + memcpy(old_customdata, v->head.data, bm->vdata.totsize); + } + + entry->swap_logvert(bm, lv->id, v, lv); + + /* Ensure id wasn't mangled in customdata swap. */ + BM_ELEM_CD_SET_INT(v, cd_id, lv->id.id); + + if (callbacks && callbacks->on_vert_change) { + callbacks->on_vert_change(v, callbacks->userdata, old_customdata); + } + } + + if (old_customdata) { + BLI_mempool_free(bm->vdata.pool, old_customdata); + } +} + +void BMLogSetDiff::restore_verts(BMesh *bm, + blender::Map, BMLogVert *> verts, + BMLogCallbacks *callbacks) +{ + for (BMLogVert *lv : verts.values()) { + BMVert *v = BM_vert_create(bm, lv->co, nullptr, BM_CREATE_NOP); + + v->head.hflag = lv->flag; + copy_v3_v3(v->no, lv->no); + + CustomData_bmesh_copy_data(&entry->vdata, &bm->vdata, lv->customdata, &v->head.data); + entry->assign_elem_id(bm, v, lv->id, true); + + if (callbacks && callbacks->on_vert_add) { + callbacks->on_vert_add(v, callbacks->userdata); + } + } + + bm->elem_index_dirty |= BM_VERT | BM_EDGE; + bm->elem_table_dirty |= BM_VERT | BM_EDGE; +} + +void BMLogSetDiff::remove_verts(BMesh *bm, + blender::Map, BMLogVert *> verts, + BMLogCallbacks *callbacks) +{ + for (BMLogVert *lv : verts.values()) { + BMVert *v = entry->get_elem_from_id(bm, lv->id); + + if (!v) { + printf("%s: Failed to find vertex %d\b", __func__, lv->id.id); + continue; + } + + if (callbacks && callbacks->on_vert_kill) { + callbacks->on_vert_kill(v, callbacks->userdata); + } + + BM_idmap_release(entry->idmap, v, false); + BM_vert_kill(bm, v); + } + + bm->elem_index_dirty |= BM_VERT | BM_EDGE; + bm->elem_table_dirty |= BM_VERT | BM_EDGE; +} + +void BMLogSetDiff::restore_edges(BMesh *bm, + blender::Map, BMLogEdge *> edges, + BMLogCallbacks *callbacks) +{ + for (BMLogEdge *le : edges.values()) { + BMVert *v1 = entry->get_elem_from_id(bm, le->v1); + BMVert *v2 = entry->get_elem_from_id(bm, le->v2); + + if (!v1) { + printf("%s: missing vertex v1 %d\n", __func__, le->v1.id); + continue; + } + + if (!v2) { + printf("%s: missing vertex v2 %d\n", __func__, le->v2.id); + continue; + } + + BMEdge *e = BM_edge_create(bm, v1, v2, nullptr, BM_CREATE_NOP); + e->head.hflag = le->flag; + + CustomData_bmesh_copy_data(&entry->edata, &bm->edata, le->customdata, &e->head.data); + + entry->assign_elem_id(bm, e, le->id, true); + + if (callbacks && callbacks->on_edge_add) { + callbacks->on_edge_add(e, callbacks->userdata); + } + } +} + +void BMLogSetDiff::remove_edges(BMesh *bm, + blender::Map, BMLogEdge *> edges, + BMLogCallbacks *callbacks) +{ + for (BMLogEdge *le : edges.values()) { + BMEdge *e = entry->get_elem_from_id(bm, le->id); + + if (!e) { + printf("%s: failed to find edge %d\n", __func__, le->id.id); + continue; + } + + if (callbacks && callbacks->on_edge_kill) { + callbacks->on_edge_kill(e, callbacks->userdata); + } + + BM_idmap_release(entry->idmap, e, true); + BM_edge_kill(bm, e); + } +} + +void BMLogSetDiff::swap_edges(BMesh *bm, + blender::Map, BMLogEdge *> edges, + BMLogCallbacks *callbacks) +{ + void *old_customdata = entry->edata.pool ? BLI_mempool_alloc(bm->edata.pool) : nullptr; + + for (BMLogEdge *le : edges.values()) { + BMEdge *e = entry->get_elem_from_id(bm, le->id); + + if (!e) { + printf("%s: failed to find edge %d\n", __func__, le->id.id); + continue; + } + + if (old_customdata) { + memcpy(old_customdata, e->head.data, bm->edata.totsize); + } + + entry->swap_logedge(bm, le->id, e, le); + + if (callbacks && callbacks->on_edge_change) { + callbacks->on_edge_change(e, callbacks->userdata, old_customdata); + } + } + + if (old_customdata) { + BLI_mempool_free(bm->edata.pool, old_customdata); + } +} + +void BMLogSetDiff::restore_faces(BMesh *bm, + blender::Map, BMLogFace *> faces, + BMLogCallbacks *callbacks) +{ + Vector verts; + + for (BMLogFace *lf : faces.values()) { + bool ok = true; + verts.clear(); + + for (BMID v_id : lf->verts) { + BMVert *v = entry->get_elem_from_id(bm, v_id); + + if (!v) { + printf("%s: Error looking up vertex %d\n", __func__, v_id.id); + ok = false; + continue; + } + + verts.append(v); + } + + if (!ok) { + continue; + } + + BMFace *f = BM_face_create_verts(bm, verts.data(), verts.size(), nullptr, BM_CREATE_NOP, true); + f->head.hflag = lf->flag; + + CustomData_bmesh_copy_data(&entry->pdata, &bm->pdata, lf->customdata, &f->head.data); + entry->assign_elem_id(bm, f, lf->id, true); + + BMLoop *l = f->l_first; + int i = 0; + + if (lf->loop_customdata[0]) { + do { + CustomData_bmesh_copy_data( + &entry->ldata, &bm->ldata, lf->loop_customdata[i], &l->head.data); + i++; + } while ((l = l->next) != f->l_first); + } + + if (callbacks && callbacks->on_face_add) { + callbacks->on_face_add(f, callbacks->userdata); + } + } + + bm->elem_index_dirty |= BM_FACE; + bm->elem_table_dirty |= BM_FACE; +} + +void BMLogSetDiff::remove_faces(BMesh *bm, + blender::Map, BMLogFace *> faces, + BMLogCallbacks *callbacks) +{ + for (BMLogFace *lf : faces.values()) { + BMFace *f = entry->get_elem_from_id(bm, lf->id); + + if (!f) { + printf("%s: error finding face %d\n", __func__, lf->id.id); + continue; + } + + if (callbacks && callbacks->on_face_kill) { + callbacks->on_face_kill(f, callbacks->userdata); + } + + BM_idmap_release(entry->idmap, f, true); + BM_face_kill(bm, f); + } + + bm->elem_index_dirty |= BM_FACE; + bm->elem_table_dirty |= BM_FACE; +} + +void BMLogSetDiff::swap_faces(BMesh *bm, + blender::Map, BMLogFace *> faces, + BMLogCallbacks *callbacks) +{ + void *old_customdata = entry->pdata.pool ? BLI_mempool_alloc(bm->pdata.pool) : nullptr; + + const int cd_id = entry->idmap->cd_id_off[BM_FACE]; + + for (BMLogFace *lf : faces.values()) { + BMFace *f = entry->get_elem_from_id(bm, lf->id); + + if (!f) { + printf("modified_faces: invalid face %d\n", lf->id.id); + continue; + } + + if (old_customdata) { + memcpy(old_customdata, f->head.data, bm->pdata.totsize); + } + + entry->swap_logface(bm, lf->id, f, lf); + + /* Ensure id wasn't mangled in customdata swap. */ + BM_ELEM_CD_SET_INT(f, cd_id, lf->id.id); + + if (callbacks && callbacks->on_face_change) { + callbacks->on_face_change(f, callbacks->userdata, old_customdata, lf->flag); + } + } + + if (old_customdata) { + BLI_mempool_free(bm->pdata.pool, old_customdata); + } +} + +void BMLogSetDiff::undo(BMesh *bm, BMLogCallbacks *callbacks) +{ + if (callbacks && callbacks->on_mesh_customdata_change) { + callbacks->on_mesh_customdata_change(&entry->vdata, BM_VERT, callbacks->userdata); + callbacks->on_mesh_customdata_change(&entry->edata, BM_EDGE, callbacks->userdata); + callbacks->on_mesh_customdata_change(&entry->ldata, BM_LOOP, callbacks->userdata); + callbacks->on_mesh_customdata_change(&entry->pdata, BM_FACE, callbacks->userdata); + } + + remove_faces(bm, added_faces, callbacks); + remove_edges(bm, added_edges, callbacks); + remove_verts(bm, added_verts, callbacks); + + restore_verts(bm, removed_verts, callbacks); + restore_edges(bm, removed_edges, callbacks); + restore_faces(bm, removed_faces, callbacks); + + swap_faces(bm, modified_faces, callbacks); + swap_edges(bm, modified_edges, callbacks); + swap_verts(bm, modified_verts, callbacks); +} + +void BMLogSetDiff::redo(BMesh *bm, BMLogCallbacks *callbacks) +{ + if (callbacks && callbacks->on_mesh_customdata_change) { + callbacks->on_mesh_customdata_change(&entry->vdata, BM_VERT, callbacks->userdata); + callbacks->on_mesh_customdata_change(&entry->edata, BM_EDGE, callbacks->userdata); + callbacks->on_mesh_customdata_change(&entry->ldata, BM_LOOP, callbacks->userdata); + callbacks->on_mesh_customdata_change(&entry->pdata, BM_FACE, callbacks->userdata); + } + + remove_faces(bm, removed_faces, callbacks); + remove_edges(bm, removed_edges, callbacks); + remove_verts(bm, removed_verts, callbacks); + + restore_verts(bm, added_verts, callbacks); + restore_edges(bm, added_edges, callbacks); + restore_faces(bm, added_faces, callbacks); + + swap_faces(bm, modified_faces, callbacks); + swap_edges(bm, modified_edges, callbacks); + swap_verts(bm, modified_verts, callbacks); +} + +static BMIdMap *entry_get_idmap(BMLogEntry *entry) +{ + return entry->idmap; +} + +BMLog *BM_log_from_existing_entries_create(BMesh *bm, BMIdMap *idmap, BMLogEntry *entry) +{ + BMLog *log = BM_log_create(bm, idmap); + log->load_entries(entry); + + return log; +} + +BMLog *BM_log_create(BMesh * /*bm*/, BMIdMap *idmap) +{ + BMLog *log = MEM_new("BMLog", idmap); + + return log; +} + +void BM_log_set_idmap(BMLog *log, struct BMIdMap *idmap) +{ + log->set_idmap(idmap); +} + +bool BM_log_is_dead(BMLog *log) +{ + return log->dead; +} + +bool BM_log_free(BMLog *log) +{ + BMLogEntry *entry = log->first_entry; + + while (entry) { + entry->log = nullptr; + entry = entry->next; + } + + MEM_delete(log); + return true; +} + +BMLogEntry *BM_log_entry_add_delta_set(BMesh *bm, BMLog *log) +{ + if (!log->current_entry) { + log->push_entry(bm); + } + else { + log->current_entry->push_set(bm, LOG_SET_DIFF); + } + + return log->current_entry; +} + +BMLogEntry *BM_log_entry_add(BMesh *bm, BMLog *log) +{ + log->push_entry(bm)->push_set(bm, LOG_SET_DIFF); + return log->current_entry; +} + +void BM_log_vert_added(BMesh *bm, BMLog *log, BMVert *v) +{ + log->add_vert(bm, v); +} + +void BM_log_vert_removed(BMesh *bm, BMLog *log, BMVert *v) +{ + log->remove_vert(bm, v); +} + +void BM_log_vert_if_modified(BMesh *bm, BMLog *log, BMVert *v) +{ + log->modify_if_vert(bm, v); +} + +void BM_log_vert_modified(BMesh *bm, BMLog *log, BMVert *v) +{ + log->modify_vert(bm, v); +} + +BMLogEntry *BM_log_entry_check_customdata(BMesh *bm, BMLog *log) { BMLogEntry *entry = log->current_entry; if (!entry) { - /* Currently at the beginning of the undo stack, move to first entry */ - entry = static_cast(log->entries.first); - } - else if (entry->next) { - /* Move to next undo entry */ - entry = entry->next; - } - else { - /* Currently at the end of the undo stack, nothing left to redo */ - return; + fprintf(stdout, "no current entry; creating...\n"); + fflush(stdout); + return BM_log_entry_add_delta_set(bm, log); } + CustomData *cd1[4] = {&bm->vdata, &bm->edata, &bm->ldata, &bm->pdata}; + CustomData *cd2[4] = {&entry->vdata, &entry->edata, &entry->ldata, &entry->pdata}; + + for (int i = 0; i < 4; i++) { + if (!CustomData_layout_is_same(cd1[i], cd2[i])) { + fprintf(stdout, "Customdata changed for undo\n"); + fflush(stdout); + + entry->cd_layout_changed = true; + return BM_log_entry_add_delta_set(bm, log); + } + } + + return entry; +} + +void BM_log_edge_added(BMesh *bm, BMLog *log, BMEdge *e) +{ + log->add_edge(bm, e); +} +void BM_log_edge_modified(BMesh *bm, BMLog *log, BMEdge *e) +{ + log->modify_edge(bm, e); +} +void BM_log_edge_removed(BMesh *bm, BMLog *log, BMEdge *e) +{ + log->remove_edge(bm, e); +} + +void BM_log_face_added(BMesh *bm, BMLog *log, BMFace *f) +{ + log->add_face(bm, f); +} +void BM_log_face_modified(BMesh *bm, BMLog *log, BMFace *f) +{ + log->modify_face(bm, f); +} +void BM_log_face_if_modified(BMesh *bm, BMLog *log, BMFace *f) +{ + log->modify_if_face(bm, f); +} +void BM_log_face_removed(BMesh *bm, BMLog *log, BMFace *f) +{ + log->remove_face(bm, f); +} +void BM_log_face_removed_no_check(BMesh *bm, BMLog *log, BMFace *f) +{ + log->remove_face(bm, f, true); +} + +void BM_log_full_mesh(BMesh *bm, BMLog *log) +{ + log->full_mesh(bm); +} + +BMVert *BM_log_id_vert_get(BMesh * /*bm*/, BMLog *log, uint id) +{ + return BM_idmap_lookup(log->idmap, id); +} + +uint BM_log_vert_id_get(BMesh * /*bm*/, BMLog *log, BMVert *v) +{ + return BM_idmap_get_id(log->idmap, v); +} + +BMFace *BM_log_id_face_get(BMesh * /*bm*/, BMLog *log, uint id) +{ + return BM_idmap_lookup(log->idmap, id); +} + +uint BM_log_face_id_get(BMesh * /*bm*/, BMLog *log, BMFace *f) +{ + return BM_idmap_get_id(log->idmap, f); +} + +int BM_log_entry_size(BMLogEntry *entry) +{ + return entry->calc_size(); +} + +void BM_log_undo(BMesh *bm, BMLog *log, BMLogCallbacks *callbacks) +{ + log->undo(bm, callbacks); +} + +void BM_log_redo(BMesh *bm, BMLog *log, BMLogCallbacks *callbacks) +{ + log->redo(bm, callbacks); +} + +void BM_log_undo_skip(BMesh * /*bm*/, BMLog *log) +{ + log->skip(-1); +} + +void BM_log_redo_skip(BMesh * /*bm*/, BMLog *log) +{ + log->skip(1); +} + +BMLogEntry *BM_log_entry_prev(BMLogEntry *entry) +{ + return entry->prev; +} + +BMLogEntry *BM_log_entry_next(BMLogEntry *entry) +{ + return entry->next; +} + +void BM_log_set_current_entry(BMLog *log, BMLogEntry *entry) +{ log->current_entry = entry; +} - if (entry) { - /* Re-delete previously deleted faces and verts */ - bm_log_faces_unmake(bm, log, entry->deleted_faces); - bm_log_verts_unmake(bm, log, entry->deleted_verts); +bool BM_log_entry_drop(BMLogEntry *entry) +{ + printf("%s\n", __func__); - /* Restore previously added verts and faces */ - bm_log_verts_restore(bm, log, entry->added_verts); - bm_log_faces_restore(bm, log, entry->added_faces); - - /* Restore vertex coordinates, mask, and hflag */ - bm_log_vert_values_swap(bm, log, entry->modified_verts); - bm_log_face_values_swap(log, entry->modified_faces); + if (entry->prev) { + entry->prev->next = entry->next; } -} - -void BM_log_vert_before_modified(BMLog *log, BMVert *v, const int cd_vert_mask_offset) -{ - BMLogEntry *entry = log->current_entry; - BMLogVert *lv; - uint v_id = bm_log_vert_id_get(log, v); - void *key = POINTER_FROM_UINT(v_id); - void **val_p; - - /* Find or create the BMLogVert entry */ - if ((lv = static_cast(BLI_ghash_lookup(entry->added_verts, key)))) { - bm_log_vert_bmvert_copy(lv, v, cd_vert_mask_offset); + if (entry->next) { + entry->next->prev = entry->prev; } - else if (!BLI_ghash_ensure_p(entry->modified_verts, key, &val_p)) { - lv = bm_log_vert_alloc(log, v, cd_vert_mask_offset); - *val_p = lv; - } -} -void BM_log_vert_added(BMLog *log, BMVert *v, const int cd_vert_mask_offset) -{ - BMLogVert *lv; - uint v_id = range_tree_uint_take_any(log->unused_ids); - void *key = POINTER_FROM_UINT(v_id); + if (entry->log) { + if (entry == entry->log->current_entry) { + entry->log->current_entry = entry->prev; + } - bm_log_vert_id_set(log, v, v_id); - lv = bm_log_vert_alloc(log, v, cd_vert_mask_offset); - BLI_ghash_insert(log->current_entry->added_verts, key, lv); -} - -void BM_log_face_modified(BMLog *log, BMFace *f) -{ - BMLogFace *lf; - uint f_id = bm_log_face_id_get(log, f); - void *key = POINTER_FROM_UINT(f_id); - - lf = bm_log_face_alloc(log, f); - BLI_ghash_insert(log->current_entry->modified_faces, key, lf); -} - -void BM_log_face_added(BMLog *log, BMFace *f) -{ - BMLogFace *lf; - uint f_id = range_tree_uint_take_any(log->unused_ids); - void *key = POINTER_FROM_UINT(f_id); - - /* Only triangles are supported for now */ - BLI_assert(f->len == 3); - - bm_log_face_id_set(log, f, f_id); - lf = bm_log_face_alloc(log, f); - BLI_ghash_insert(log->current_entry->added_faces, key, lf); -} - -void BM_log_vert_removed(BMLog *log, BMVert *v, const int cd_vert_mask_offset) -{ - BMLogEntry *entry = log->current_entry; - uint v_id = bm_log_vert_id_get(log, v); - void *key = POINTER_FROM_UINT(v_id); - - /* if it has a key, it shouldn't be nullptr */ - BLI_assert(!!BLI_ghash_lookup(entry->added_verts, key) == - !!BLI_ghash_haskey(entry->added_verts, key)); - - if (BLI_ghash_remove(entry->added_verts, key, nullptr, nullptr)) { - range_tree_uint_release(log->unused_ids, v_id); - } - else { - BMLogVert *lv, *lv_mod; - - lv = bm_log_vert_alloc(log, v, cd_vert_mask_offset); - BLI_ghash_insert(entry->deleted_verts, key, lv); - - /* If the vertex was modified before deletion, ensure that the - * original vertex values are stored */ - if ((lv_mod = static_cast(BLI_ghash_lookup(entry->modified_verts, key)))) { - (*lv) = (*lv_mod); - BLI_ghash_remove(entry->modified_verts, key, nullptr, nullptr); + if (entry == entry->log->first_entry) { + entry->log->first_entry = entry->next; } } + + MEM_delete(entry); + return true; } -void BM_log_face_removed(BMLog *log, BMFace *f) +void BM_log_print_entry(BMLog * /*log*/, BMLogEntry *entry) { - BMLogEntry *entry = log->current_entry; - uint f_id = bm_log_face_id_get(log, f); - void *key = POINTER_FROM_UINT(f_id); - - /* if it has a key, it shouldn't be nullptr */ - BLI_assert(!!BLI_ghash_lookup(entry->added_faces, key) == - !!BLI_ghash_haskey(entry->added_faces, key)); - - if (BLI_ghash_remove(entry->added_faces, key, nullptr, nullptr)) { - range_tree_uint_release(log->unused_ids, f_id); - } - else { - BMLogFace *lf; - - lf = bm_log_face_alloc(log, f); - BLI_ghash_insert(entry->deleted_faces, key, lf); - } -} - -void BM_log_all_added(BMesh *bm, BMLog *log) -{ - const int cd_vert_mask_offset = CustomData_get_offset_named( - &bm->vdata, CD_PROP_FLOAT, ".sculpt_mask"); - BMIter bm_iter; - BMVert *v; - BMFace *f; - - /* avoid unnecessary resizing on initialization */ - if (BLI_ghash_len(log->current_entry->added_verts) == 0) { - BLI_ghash_reserve(log->current_entry->added_verts, uint(bm->totvert)); - } - - if (BLI_ghash_len(log->current_entry->added_faces) == 0) { - BLI_ghash_reserve(log->current_entry->added_faces, uint(bm->totface)); - } - - /* Log all vertices as newly created */ - BM_ITER_MESH (v, &bm_iter, bm, BM_VERTS_OF_MESH) { - BM_log_vert_added(log, v, cd_vert_mask_offset); - } - - /* Log all faces as newly created */ - BM_ITER_MESH (f, &bm_iter, bm, BM_FACES_OF_MESH) { - BM_log_face_added(log, f); - } -} - -void BM_log_before_all_removed(BMesh *bm, BMLog *log) -{ - const int cd_vert_mask_offset = CustomData_get_offset_named( - &bm->vdata, CD_PROP_FLOAT, ".sculpt_mask"); - BMIter bm_iter; - BMVert *v; - BMFace *f; - - /* Log deletion of all faces */ - BM_ITER_MESH (f, &bm_iter, bm, BM_FACES_OF_MESH) { - BM_log_face_removed(log, f); - } - - /* Log deletion of all vertices */ - BM_ITER_MESH (v, &bm_iter, bm, BM_VERTS_OF_MESH) { - BM_log_vert_removed(log, v, cd_vert_mask_offset); - } -} - -const float *BM_log_find_original_vert_co(BMLog *log, BMVert *v) -{ - BMLogEntry *entry = log->current_entry; - const BMLogVert *lv; - uint v_id = bm_log_vert_id_get(log, v); - void *key = POINTER_FROM_UINT(v_id); - - lv = static_cast(BLI_ghash_lookup(entry->modified_verts, key)); - return lv == nullptr ? nullptr : lv->co; -} - -const float *BM_log_original_vert_co(BMLog *log, BMVert *v) -{ - BMLogEntry *entry = log->current_entry; - const BMLogVert *lv; - uint v_id = bm_log_vert_id_get(log, v); - void *key = POINTER_FROM_UINT(v_id); - - BLI_assert(entry); - - BLI_assert(BLI_ghash_haskey(entry->modified_verts, key)); - - lv = static_cast(BLI_ghash_lookup(entry->modified_verts, key)); - return lv->co; -} - -const float *BM_log_original_vert_no(BMLog *log, BMVert *v) -{ - BMLogEntry *entry = log->current_entry; - const BMLogVert *lv; - uint v_id = bm_log_vert_id_get(log, v); - void *key = POINTER_FROM_UINT(v_id); - - BLI_assert(entry); - - BLI_assert(BLI_ghash_haskey(entry->modified_verts, key)); - - lv = static_cast(BLI_ghash_lookup(entry->modified_verts, key)); - return lv->no; -} - -float BM_log_original_mask(BMLog *log, BMVert *v) -{ - BMLogEntry *entry = log->current_entry; - const BMLogVert *lv; - uint v_id = bm_log_vert_id_get(log, v); - void *key = POINTER_FROM_UINT(v_id); - - BLI_assert(entry); - - BLI_assert(BLI_ghash_haskey(entry->modified_verts, key)); - - lv = static_cast(BLI_ghash_lookup(entry->modified_verts, key)); - return lv->mask; -} - -void BM_log_original_vert_data(BMLog *log, BMVert *v, const float **r_co, const float **r_no) -{ - BMLogEntry *entry = log->current_entry; - const BMLogVert *lv; - uint v_id = bm_log_vert_id_get(log, v); - void *key = POINTER_FROM_UINT(v_id); - - BLI_assert(entry); - - BLI_assert(BLI_ghash_haskey(entry->modified_verts, key)); - - lv = static_cast(BLI_ghash_lookup(entry->modified_verts, key)); - *r_co = lv->co; - *r_no = lv->no; -} - -/************************ Debugging and Testing ***********************/ - -BMLogEntry *BM_log_current_entry(BMLog *log) -{ - return log->current_entry; -} - -RangeTreeUInt *BM_log_unused_ids(BMLog *log) -{ - return log->unused_ids; -} - -#if 0 -/* Print the list of entries, marking the current one - * - * Keep around for debugging */ -void bm_log_print(const BMLog *log, const char *description) -{ - const BMLogEntry *entry; - const char *current = " <-- current"; - int i; - - printf("%s:\n", description); - printf(" % 2d: [ initial ]%s\n", 0, (!log->current_entry) ? current : ""); - for (entry = log->entries.first, i = 1; entry; entry = entry->next, i++) { - printf(" % 2d: [%p]%s\n", i, entry, (entry == log->current_entry) ? current : ""); - } -} -#endif - -void BM_log_print_entry(BMesh *bm, BMLogEntry *entry) -{ - if (bm) { - printf("BM { totvert=%d totedge=%d totloop=%d faces_num=%d\n", - bm->totvert, - bm->totedge, - bm->totloop, - bm->totface); - - if (!bm->totvert) { - printf("%s: Warning: empty bmesh\n", __func__); - } - } - else { - printf("BM { totvert=unknown totedge=unknown totloop=unknown faces_num=unknown\n"); - } - - printf("v | added: %d, removed: %d, modified: %d\n", - int(BLI_ghash_len(entry->added_verts)), - int(BLI_ghash_len(entry->deleted_verts)), - int(BLI_ghash_len(entry->modified_verts))); - printf("f | added: %d, removed: %d, modified: %d\n", - int(BLI_ghash_len(entry->added_faces)), - int(BLI_ghash_len(entry->deleted_faces)), - int(BLI_ghash_len(entry->modified_faces))); - printf("}\n"); + printf("BMLogEntry: %p", entry); + entry->print(); + printf("\n"); } diff --git a/source/blender/bmesh/intern/bmesh_log.hh b/source/blender/bmesh/intern/bmesh_log.hh index 302bd8f784e..9a970701462 100644 --- a/source/blender/bmesh/intern/bmesh_log.hh +++ b/source/blender/bmesh/intern/bmesh_log.hh @@ -4,218 +4,141 @@ #pragma once +#include "BLI_sys_types.h" + /** \file * \ingroup bmesh + * + * `BMLog` is the undo system used by DynTopo. Changes + * to a `BMesh` are stored incrementally. + * + * The following operations are supported for logging: + * - Adding and removing vertices + * - Adding and removing edges + * - Adding and removing faces + * - Attribute changes. + * - Element header flags. + * + * Internal details: + * + * Each sculpt undo step owns a pointer to a `BMLogEntry`. + * Every `BMLogEntry` in turn has a list of log sets. + * + * A log set is a subclass of `BMLogSetBase` and can be + * either a delta set (`BMLogSetDiff`) or a full mesh + * (BMLogSetFull). + * + * Particuarly complex mesh operations can sometimes benefit from + * having a clean `BMLogSetDiff` set, this helps avoid corrupting + * element IDs. This can be done with `BM_log_entry_add_delta_set`. + * + * To log a complete mesh, use `BM_log_full_mesh`. */ -struct BMFace; -struct BMVert; struct BMesh; -struct RangeTreeUInt; +struct BMEdge; +struct BMElem; +struct BMFace; +struct BMIdMap; struct BMLog; struct BMLogEntry; +struct BMVert; +struct CustomData; -/** - * Allocate, initialize, and assign a new BMLog. +struct BMLogCallbacks { + void (*on_vert_add)(BMVert *v, void *userdata); + void (*on_vert_kill)(BMVert *v, void *userdata); + void (*on_vert_change)(BMVert *v, void *userdata, void *old_customdata); + + void (*on_edge_add)(BMEdge *e, void *userdata); + void (*on_edge_kill)(BMEdge *e, void *userdata); + void (*on_edge_change)(BMEdge *e, void *userdata, void *old_customdata); + + void (*on_face_add)(BMFace *f, void *userdata); + void (*on_face_kill)(BMFace *f, void *userdata); + void (*on_face_change)(BMFace *f, void *userdata, void *old_customdata, char old_hflag); + + void (*on_full_mesh_load)(void *userdata); + void (*on_mesh_customdata_change)(CustomData *domain, char htype, void *userdata); + void *userdata; +}; + +/* Allocate and initialize a new BMLog */ +BMLog *BM_log_create(BMesh *bm, BMIdMap *idmap); + +/* Allocate and initialize a new BMLog using existing BMLogEntries */ -BMLog *BM_log_create(BMesh *bm); +BMLog *BM_log_from_existing_entries_create(BMesh *bm, BMIdMap *idmap, BMLogEntry *entry); -/** - * Allocate and initialize a new #BMLog using existing #BMLogEntries - * - * The 'entry' should be the last entry in the #BMLog. Its `prev` pointer - * will be followed back to find the first entry. - * - * The unused IDs field of the log will be initialized by taking all - * keys from all GHashes in the log entry. +/* Does not free the log's entries, just the BMLog itself. */ +bool BM_log_free(BMLog *log); + +/* Start a new log entry and update the log entry list */ +BMLogEntry *BM_log_entry_add(BMesh *bm, BMLog *log); + +/* + * Add a new delta set to the current log entry. If no entry + * exists one will be created. Returns current log entry. */ -BMLog *BM_log_from_existing_entries_create(BMesh *bm, BMLogEntry *entry); +BMLogEntry *BM_log_entry_add_delta_set(BMesh *bm, BMLog *log); -/** - * Free all the data in a BMLog including the log itself. +/* Check if customdata layout has changed. If it has a new + * subentry will be pushed so any further logging will have + * the correct customdata. */ -void BM_log_free(BMLog *log); +BMLogEntry *BM_log_entry_check_customdata(BMesh *bm, BMLog *log); -/** - * Get the number of log entries. - */ -int BM_log_length(const BMLog *log); +/* Undo one BMLogEntry. */ +void BM_log_undo(BMesh *bm, BMLog *log, BMLogCallbacks *callbacks); +/* Skip one BMLogEntry. */ +void BM_log_undo_skip(BMesh *bm, BMLog *log); -/** Apply a consistent ordering to BMesh vertices and faces. */ -void BM_log_mesh_elems_reorder(BMesh *bm, BMLog *log); +/* Redo one BMLogEntry */ +void BM_log_redo(BMesh *bm, BMLog *log, BMLogCallbacks *callbacks); +/* Skip one BMLogEntry. */ +void BM_log_redo_skip(BMesh *bm, BMLog *log); -/** - * Start a new log entry and update the log entry list. - * - * If the log entry list is empty, or if the current log entry is the - * last entry, the new entry is simply appended to the end. - * - * Otherwise, the new entry is added after the current entry and all - * following entries are deleted. - * - * In either case, the new entry is set as the current log entry. - */ -BMLogEntry *BM_log_entry_add(BMLog *log); +/* Removes and deallocates a log entry. */ +bool BM_log_entry_drop(BMLogEntry *entry); +void BM_log_set_idmap(BMLog *log, BMIdMap *idmap); -/** Mark all used ids as unused for this node */ -void BM_log_cleanup_entry(BMLogEntry *entry); +/* Log a vertex if it hasn't been logged in this undo step yet. */ +void BM_log_vert_if_modified(BMesh *bm, BMLog *log, BMVert *v); +void BM_log_vert_modified(BMesh *bm, BMLog *log, BMVert *v); -/** - * Remove an entry from the log. - * - * Uses entry->log as the log. If the log is NULL, the entry will be - * freed but not removed from any list, nor shall its IDs be released. - * - * This operation is only valid on the first and last entries in the - * log. Deleting from the middle will assert. - */ -void BM_log_entry_drop(BMLogEntry *entry); - -/** - * Undo one #BMLogEntry. - * - * Has no effect if there's nothing left to undo. - */ -void BM_log_undo(BMesh *bm, BMLog *log); - -/** - * Redo one #BMLogEntry. - * - * Has no effect if there's nothing left to redo. - */ -void BM_log_redo(BMesh *bm, BMLog *log); - -/** - * Log a vertex before it is modified. - * - * Before modifying vertex coordinates, masks, or hflags, call this - * function to log its current values. This is better than logging - * after the coordinates have been modified, because only those - * vertices that are modified need to have their original values - * stored. - * - * Handles two separate cases: - * - * If the vertex was added in the current log entry, update the - * vertex in the map of added vertices. - * - * If the vertex already existed prior to the current log entry, a - * separate key/value map of modified vertices is used (using the - * vertex's ID as the key). The values stored in that case are - * the vertex's original state so that an undo can restore the - * previous state. - * - * On undo, the current vertex state will be swapped with the stored - * state so that a subsequent redo operation will restore the newer - * vertex state. - */ -void BM_log_vert_before_modified(BMLog *log, BMVert *v, int cd_vert_mask_offset); - -/** - * Log a new vertex as added to the #BMesh. +/* Log a new vertex as added to the BMesh * * The new vertex gets a unique ID assigned. It is then added to a map * of added vertices, with the key being its ID and the value * containing everything needed to reconstruct that vertex. */ -void BM_log_vert_added(BMLog *log, BMVert *v, int cd_vert_mask_offset); +void BM_log_vert_added(BMesh *bm, BMLog *log, BMVert *v); +/* Log a vertex as removed from the BMesh */ +void BM_log_vert_removed(BMesh *bm, BMLog *log, BMVert *v); -/** - * Log a face before it is modified. - * - * This is intended to handle only header flags and we always - * assume face has been added before. +/* Log a new edge as added to the BMesh */ +void BM_log_edge_added(BMesh *bm, BMLog *log, BMEdge *e); +/* Log an edge's flags and customdata. */ +void BM_log_edge_modified(BMesh *bm, BMLog *log, BMEdge *e); +/* Log an edge as removed from the BMesh */ +void BM_log_edge_removed(BMesh *bm, BMLog *log, BMEdge *e); + +/* Log a face's flags and customdata. */ +void BM_log_face_modified(BMesh *bm, BMLog *log, BMFace *f); +/* Log a face's flags and customdata if it doesn't exist in the log already. */ +void BM_log_face_if_modified(BMesh *bm, BMLog *log, BMFace *f); +/* Log a new face as added to the BMesh. */ +void BM_log_face_added(BMesh *bm, BMLog *log, BMFace *f); +/* Log a face as removed from the BMesh */ +void BM_log_face_removed(BMesh *bm, BMLog *log, BMFace *f); +/* Logs a face as removed without checking if it's already been logged.*/ +void BM_log_face_removed_no_check(BMesh *bm, BMLog *log, BMFace *f); + +/* Log the complete mesh, will be stored as + * a Mesh copy. */ -void BM_log_face_modified(BMLog *log, BMFace *f); +void BM_log_full_mesh(BMesh *bm, BMLog *log); -/** - * Log a new face as added to the #BMesh. - * - * The new face gets a unique ID assigned. It is then added to a map - * of added faces, with the key being its ID and the value containing - * everything needed to reconstruct that face. - */ -void BM_log_face_added(BMLog *log, BMFace *f); - -/** - * Log a vertex as removed from the #BMesh. - * - * A couple things can happen here: - * - * If the vertex was added as part of the current log entry, then it's - * deleted and forgotten about entirely. Its unique ID is returned to - * the unused pool. - * - * If the vertex was already part of the #BMesh before the current log - * entry, it is added to a map of deleted vertices, with the key being - * its ID and the value containing everything needed to reconstruct - * that vertex. - * - * If there's a move record for the vertex, that's used as the - * vertices original location, then the move record is deleted. - */ -void BM_log_vert_removed(BMLog *log, BMVert *v, int cd_vert_mask_offset); - -/** - * Log a face as removed from the #BMesh. - * - * A couple things can happen here: - * - * If the face was added as part of the current log entry, then it's - * deleted and forgotten about entirely. Its unique ID is returned to - * the unused pool. - * - * If the face was already part of the #BMesh before the current log - * entry, it is added to a map of deleted faces, with the key being - * its ID and the value containing everything needed to reconstruct - * that face. - */ -void BM_log_face_removed(BMLog *log, BMFace *f); - -/** - * Log all vertices/faces in the #BMesh as added. - */ -void BM_log_all_added(BMesh *bm, BMLog *log); - -/** Log all vertices/faces in the #BMesh as removed. */ -void BM_log_before_all_removed(BMesh *bm, BMLog *log); - -/** - * Search the log for the original vertex coordinates. - * - * Does not modify the log or the vertex. - * - * \return the pointer or nullptr if the vertex isn't found. - */ -const float *BM_log_find_original_vert_co(BMLog *log, BMVert *v); - -/** - * Get the logged coordinates of a vertex. - * - * Does not modify the log or the vertex. - */ -const float *BM_log_original_vert_co(BMLog *log, BMVert *v); - -/** - * Get the logged normal of a vertex - * - * Does not modify the log or the vertex. - */ -const float *BM_log_original_vert_no(BMLog *log, BMVert *v); - -/** - * Get the logged mask of a vertex - * - * Does not modify the log or the vertex. - */ -float BM_log_original_mask(BMLog *log, BMVert *v); - -/** Get the logged data of a vertex (avoid multiple lookups). */ -void BM_log_original_vert_data(BMLog *log, BMVert *v, const float **r_co, const float **r_no); - -/** For internal use only (unit testing). */ -BMLogEntry *BM_log_current_entry(BMLog *log); -/** For internal use only (unit testing) */ -struct RangeTreeUInt *BM_log_unused_ids(BMLog *log); - -void BM_log_print_entry(BMesh *bm, BMLogEntry *entry); +/* Called from sculpt undo code. */ +void BM_log_print_entry(BMLog *log, BMLogEntry *entry); +int BM_log_entry_size(BMLogEntry *entry); diff --git a/source/blender/bmesh/intern/bmesh_mesh.hh b/source/blender/bmesh/intern/bmesh_mesh.hh index 631ebf1aa2c..20887d1753c 100644 --- a/source/blender/bmesh/intern/bmesh_mesh.hh +++ b/source/blender/bmesh/intern/bmesh_mesh.hh @@ -203,3 +203,7 @@ void BM_mesh_vert_coords_apply(BMesh *bm, const float (*vert_coords)[3]); void BM_mesh_vert_coords_apply_with_mat4(BMesh *bm, const float (*vert_coords)[3], const float mat[4][4]); + +/* Returns true if elem is freed. Used by DynTopo. + * Is ASAN-safe. */ +bool BM_elem_is_free(BMElem *elem, int htype); diff --git a/source/blender/bmesh/intern/bmesh_mesh_convert.cc b/source/blender/bmesh/intern/bmesh_mesh_convert.cc index a199a0b0975..696672a1cb5 100644 --- a/source/blender/bmesh/intern/bmesh_mesh_convert.cc +++ b/source/blender/bmesh/intern/bmesh_mesh_convert.cc @@ -105,6 +105,7 @@ #include "intern/bmesh_private.hh" /* For element checking. */ #include "CLG_log.h" +#include static CLG_LogRef LOG = {"bmesh.mesh.convert"}; @@ -154,10 +155,64 @@ static BMFace *bm_face_create_from_mpoly(BMesh &bm, return BM_face_create(&bm, verts.data(), edges.data(), size, nullptr, BM_CREATE_SKIP_CD); } +using NoCopyLayerVector = blender::Vector>; + +static NoCopyLayerVector unmark_temp_cdlayers(CustomData *domains[4]) +{ + NoCopyLayerVector nocopy_list; + + for (int i = 0; i < 4; i++) { + CustomData *data = domains[i]; + + for (CustomDataLayer &layer : + blender::MutableSpan(data->layers, data->totlayer)) { + if ((layer.flag & CD_FLAG_TEMPORARY) && (layer.flag & CD_FLAG_NOCOPY)) { + layer.flag &= ~CD_FLAG_NOCOPY; + nocopy_list.append(std::make_pair(layer, int(1 << i))); + } + } + } + + return nocopy_list; +} + +static void restore_cd_copy_flags(CustomData *domains[4], NoCopyLayerVector &nocopy_list) +{ + for (std::pair &pair : nocopy_list) { + CustomData *data = nullptr; + + switch (pair.second) { + case BM_VERT: + data = domains[0]; + break; + case BM_EDGE: + data = domains[1]; + break; + case BM_LOOP: + data = domains[2]; + break; + case BM_FACE: + data = domains[3]; + break; + } + + CustomDataLayer &layer = pair.first; + int idx = CustomData_get_named_layer_index(data, eCustomDataType(layer.type), layer.name); + + if (idx == -1) { + printf("Error: missing temporary attribute %s\n", layer.name); + continue; + } + + data->layers[idx].flag |= CD_FLAG_NOCOPY; + } +} + struct MeshToBMeshLayerInfo { eCustomDataType type; /** The layer's position in the BMesh element's data block. */ int bmesh_offset; + int n; /** The mesh's #CustomDataLayer::data. When null, the BMesh block is set to its default value. */ const void *mesh_data; /** The size of every custom data element. */ @@ -185,7 +240,7 @@ static Vector mesh_to_bm_copy_info_calc(const CustomData & info.type = type; info.bmesh_offset = bm_layer.offset; info.mesh_data = (mesh_layer_index == -1) ? nullptr : mesh_data.layers[mesh_layer_index].data; - info.elem_size = CustomData_get_elem_size(&bm_layer); + info.elem_size = CustomData_sizeof(type); infos.append(info); per_type_index[type]++; @@ -234,6 +289,14 @@ void BM_mesh_bm_from_me(BMesh *bm, const Mesh *mesh, const BMeshFromMeshParams * CustomData mesh_ldata = CustomData_shallow_copy_remove_non_bmesh_attributes(&mesh->corner_data, mask.lmask); + CustomData *mesh_domains[4] = {&mesh_vdata, &mesh_edata, &mesh_ldata, &mesh_pdata}; + CustomData *bmesh_domains[4] = {&bm->vdata, &bm->edata, &bm->ldata, &bm->pdata}; + NoCopyLayerVector nocopy_layers; + + if (params && params->copy_temp_cdlayers) { + nocopy_layers = unmark_temp_cdlayers(mesh_domains); + } + blender::Vector temporary_layers_to_delete; for (const int layer_index : @@ -287,6 +350,11 @@ void BM_mesh_bm_from_me(BMesh *bm, const Mesh *mesh, const BMeshFromMeshParams * CustomData_bmesh_init_pool(&bm->ldata, mesh->corners_num, BM_LOOP); CustomData_bmesh_init_pool(&bm->pdata, mesh->faces_num, BM_FACE); } + + if (params && params->copy_temp_cdlayers) { + restore_cd_copy_flags(bmesh_domains, nocopy_layers); + } + return; } @@ -486,6 +554,7 @@ void BM_mesh_bm_from_me(BMesh *bm, const Mesh *mesh, const BMeshFromMeshParams * BM_elem_flag_enable(e, BM_ELEM_SMOOTH); } + /* Copy Custom Data */ mesh_attributes_copy_to_bmesh_block(bm->edata, edge_info, i, e->head); } if (is_new) { @@ -596,6 +665,11 @@ void BM_mesh_bm_from_me(BMesh *bm, const Mesh *mesh, const BMeshFromMeshParams * else { BM_select_history_clear(bm); } + + if (params && params->copy_temp_cdlayers) { + restore_cd_copy_flags(mesh_domains, nocopy_layers); + restore_cd_copy_flags(bmesh_domains, nocopy_layers); + } } /** @@ -1048,6 +1122,7 @@ static void bmesh_to_mesh_calc_object_remap(Main &bmain, struct BMeshToMeshLayerInfo { eCustomDataType type; + int n; /* Per-type index. */ /** The layer's position in the BMesh element's data block. */ int bmesh_offset; /** The mesh's #CustomDataLayer::data. When null, the BMesh block is set to its default value. */ @@ -1085,6 +1160,7 @@ static Vector bm_to_mesh_copy_info_calc(const CustomData & BMeshToMeshLayerInfo info{}; info.type = type; + info.n = per_type_index[type]; info.bmesh_offset = bm_layer.offset; info.mesh_data = mesh_layer.data; info.elem_size = CustomData_get_elem_size(&mesh_layer); @@ -1413,6 +1489,12 @@ void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *mesh, const BMeshToMeshParam using namespace blender; const int old_verts_num = mesh->verts_num; + CustomData *bmesh_domains[4] = {&bm->vdata, &bm->edata, &bm->ldata, &bm->pdata}; + NoCopyLayerVector nocopy_layers; + if (params->copy_temp_cdlayers) { + nocopy_layers = unmark_temp_cdlayers(bmesh_domains); + } + BKE_mesh_clear_geometry(mesh); mesh->verts_num = bm->totvert; @@ -1620,6 +1702,13 @@ void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *mesh, const BMeshToMeshParam hide_poly.finish(); sharp_face.finish(); material_index.finish(); + + if (params && params->copy_temp_cdlayers) { + CustomData *mesh_domains[4] = {&mesh->vert_data, &mesh->edge_data, &mesh->corner_data, &mesh->face_data}; + + restore_cd_copy_flags(bmesh_domains, nocopy_layers); + restore_cd_copy_flags(mesh_domains, nocopy_layers); + } } void BM_mesh_bm_to_me_compact(BMesh &bm, diff --git a/source/blender/bmesh/intern/bmesh_mesh_convert.hh b/source/blender/bmesh/intern/bmesh_mesh_convert.hh index e36bbc77b79..03dec689b45 100644 --- a/source/blender/bmesh/intern/bmesh_mesh_convert.hh +++ b/source/blender/bmesh/intern/bmesh_mesh_convert.hh @@ -32,7 +32,9 @@ struct BMeshFromMeshParams { /* define the active shape key (index + 1) */ int active_shapekey; struct CustomData_MeshMasks cd_mask_extra; + uint copy_temp_cdlayers; }; + /** * \brief Mesh -> BMesh * \param bm: The mesh to write into, while this is typically a newly created BMesh, @@ -65,6 +67,7 @@ struct BMeshToMeshParams { */ bool active_shapekey_to_mvert; struct CustomData_MeshMasks cd_mask_extra; + uint copy_temp_cdlayers; }; /** diff --git a/source/blender/bmesh/intern/bmesh_mods.cc b/source/blender/bmesh/intern/bmesh_mods.cc index 770d6c00b98..d6cff524fb3 100644 --- a/source/blender/bmesh/intern/bmesh_mods.cc +++ b/source/blender/bmesh/intern/bmesh_mods.cc @@ -439,9 +439,9 @@ BMEdge *BM_vert_collapse_edge(BMesh *bm, #undef DO_V_INTERP BMVert *BM_edge_collapse( - BMesh *bm, BMEdge *e_kill, BMVert *v_kill, const bool do_del, const bool kill_degenerate_faces) + BMesh *bm, BMEdge *e_kill, BMVert *v_kill, bool do_del, bool /*kill_degenerate_faces*/) { - return bmesh_kernel_join_vert_kill_edge(bm, e_kill, v_kill, do_del, true, kill_degenerate_faces); + return bmesh_kernel_join_vert_kill_edge(bm, e_kill, v_kill, do_del); } BMVert *BM_edge_split(BMesh *bm, BMEdge *e, BMVert *v, BMEdge **r_e, float fac) diff --git a/source/blender/bmesh/intern/bmesh_structure.cc b/source/blender/bmesh/intern/bmesh_structure.cc index dd28f0f925c..9d78a1150f4 100644 --- a/source/blender/bmesh/intern/bmesh_structure.cc +++ b/source/blender/bmesh/intern/bmesh_structure.cc @@ -8,6 +8,7 @@ * Low level routines for manipulating the BM structure. */ +#include "BLI_asan.h" #include "BLI_utildefines.h" #include "bmesh.hh" @@ -567,3 +568,26 @@ bool bmesh_loop_validate(BMFace *f) return true; } + +static int sizes[] = {-1, + (int)sizeof(BMVert), + (int)sizeof(BMEdge), + 0, + (int)sizeof(BMLoop), + -1, + -1, + -1, + (int)sizeof(BMFace)}; + +bool BM_elem_is_free(BMElem *elem, int htype) +{ + BLI_asan_unpoison(elem, sizes[htype]); + + bool ret = elem->head.htype != htype; + + if (ret) { + BLI_asan_poison(elem, sizes[htype]); + } + + return ret; +} diff --git a/source/blender/draw/DRW_pbvh.hh b/source/blender/draw/DRW_pbvh.hh index 97f3e7fb16e..b2e1ebba0c2 100644 --- a/source/blender/draw/DRW_pbvh.hh +++ b/source/blender/draw/DRW_pbvh.hh @@ -20,6 +20,8 @@ #include "DNA_customdata_types.h" #include "BKE_ccg.h" +#include "BKE_dyntopo_set.hh" +#include "BKE_pbvh_api.hh" struct GPUBatch; struct PBVHNode; @@ -32,6 +34,8 @@ namespace blender::bke { enum class AttrDomain : int8_t; } +using blender::bke::dyntopo::DyntopoSet; + namespace blender::draw::pbvh { class GenericRequest { @@ -60,6 +64,10 @@ struct PBVHBatches; struct PBVH_GPU_Args { int pbvh_type; + const PBVHTriBuf *tribuf; + Span tri_buffers; + bool show_orig; + BMesh *bm; const Mesh *mesh; MutableSpan vert_positions; @@ -90,7 +98,7 @@ struct PBVH_GPU_Args { Span tri_faces; /* BMesh. */ - const Set *bm_faces; + DyntopoSet *bm_faces; int cd_mask_layer; }; diff --git a/source/blender/draw/intern/draw_pbvh.cc b/source/blender/draw/intern/draw_pbvh.cc index 138ca2cf50b..e0b86558f11 100644 --- a/source/blender/draw/intern/draw_pbvh.cc +++ b/source/blender/draw/intern/draw_pbvh.cc @@ -208,17 +208,19 @@ void extract_data_vert_bmesh(const PBVH_GPU_Args &args, const int cd_offset, GPU using VBOType = typename Converter::VBOType; VBOType *data = static_cast(GPU_vertbuf_get_data(&vbo)); - for (const BMFace *f : *args.bm_faces) { + for (const PBVHTri &tri : args.tribuf->tris) { + BMFace *f = reinterpret_cast(tri.f.i); if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { continue; } - const BMLoop *l = f->l_first; - *data = Converter::convert(bmesh_cd_vert_get(*l->prev->v, cd_offset)); - data++; - *data = Converter::convert(bmesh_cd_vert_get(*l->v, cd_offset)); - data++; - *data = Converter::convert(bmesh_cd_vert_get(*l->next->v, cd_offset)); - data++; + + BMVert *v1 = reinterpret_cast(args.tribuf->verts[tri.v[0]].i); + BMVert *v2 = reinterpret_cast(args.tribuf->verts[tri.v[1]].i); + BMVert *v3 = reinterpret_cast(args.tribuf->verts[tri.v[2]].i); + + *data++ = Converter::convert(bmesh_cd_vert_get(*v1, cd_offset)); + *data++ = Converter::convert(bmesh_cd_vert_get(*v2, cd_offset)); + *data++ = Converter::convert(bmesh_cd_vert_get(*v3, cd_offset)); } } @@ -229,10 +231,12 @@ void extract_data_face_bmesh(const PBVH_GPU_Args &args, const int cd_offset, GPU using VBOType = typename Converter::VBOType; VBOType *data = static_cast(GPU_vertbuf_get_data(&vbo)); - for (const BMFace *f : *args.bm_faces) { + for (const PBVHTri &tri : args.tribuf->tris) { + BMFace *f = reinterpret_cast(tri.f.i); if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { continue; } + std::fill_n(data, 3, Converter::convert(bmesh_cd_face_get(*f, cd_offset))); data += 3; } @@ -245,17 +249,19 @@ void extract_data_corner_bmesh(const PBVH_GPU_Args &args, const int cd_offset, G using VBOType = typename Converter::VBOType; VBOType *data = static_cast(GPU_vertbuf_get_data(&vbo)); - for (const BMFace *f : *args.bm_faces) { + for (const PBVHTri &tri : args.tribuf->tris) { + BMFace *f = reinterpret_cast(tri.f.i); if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { continue; } - const BMLoop *l = f->l_first; - *data = Converter::convert(bmesh_cd_loop_get(*l->prev, cd_offset)); - data++; - *data = Converter::convert(bmesh_cd_loop_get(*l, cd_offset)); - data++; - *data = Converter::convert(bmesh_cd_loop_get(*l->next, cd_offset)); - data++; + + BMLoop *l1 = reinterpret_cast(tri.l[0]); + BMLoop *l2 = reinterpret_cast(tri.l[1]); + BMLoop *l3 = reinterpret_cast(tri.l[2]); + + *data++ = Converter::convert(bmesh_cd_loop_get(*l1, cd_offset)); + *data++ = Converter::convert(bmesh_cd_loop_get(*l2, cd_offset)); + *data++ = Converter::convert(bmesh_cd_loop_get(*l3, cd_offset)); } } @@ -373,11 +379,7 @@ struct PBVHBatches { break; } case PBVH_BMESH: { - for (const BMFace *f : *args.bm_faces) { - if (!BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { - count++; - } - } + return args.tribuf->tris.size(); } } @@ -817,6 +819,7 @@ struct PBVHBatches { void *existing_data = GPU_vertbuf_get_data(vbo.vert_buf); int vert_count = tris_count * 3; + const PBVHTriBuf *tribuf = args.tribuf; if (existing_data == nullptr || existing_num != vert_count) { /* Allocate buffer if not allocated yet or size changed. */ @@ -838,40 +841,108 @@ struct PBVHBatches { switch (*request_type) { case CustomRequest::Position: { float3 *data = static_cast(GPU_vertbuf_get_data(vbo.vert_buf)); - for (const BMFace *f : *args.bm_faces) { + for (const PBVHTri &tri : tribuf->tris) { + BMFace *f = reinterpret_cast(tri.f.i); if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { continue; } - const BMLoop *l = f->l_first; - *data = l->prev->v->co; - data++; - *data = l->v->co; - data++; - *data = l->next->v->co; - data++; + + *data++ = reinterpret_cast(tribuf->verts[tri.v[0]].i)->co; + *data++ = reinterpret_cast(tribuf->verts[tri.v[1]].i)->co; + *data++ = reinterpret_cast(tribuf->verts[tri.v[2]].i)->co; } break; } case CustomRequest::Normal: { short4 *data = static_cast(GPU_vertbuf_get_data(vbo.vert_buf)); - for (const BMFace *f : *args.bm_faces) { +#if 1 + for (const PBVHTri &tri : tribuf->tris) { + BMFace *f = reinterpret_cast(tri.f.i); if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { continue; } + if (BM_elem_flag_test(f, BM_ELEM_SMOOTH)) { - const BMLoop *l = f->l_first; - *data = normal_float_to_short(l->prev->v->no); - data++; - *data = normal_float_to_short(l->v->no); - data++; - *data = normal_float_to_short(l->next->v->no); - data++; + *data++ = normal_float_to_short( + reinterpret_cast(tribuf->verts[tri.v[0]].i)->no); + *data++ = normal_float_to_short( + reinterpret_cast(tribuf->verts[tri.v[1]].i)->no); + *data++ = normal_float_to_short( + reinterpret_cast(tribuf->verts[tri.v[2]].i)->no); } else { std::fill_n(data, 3, normal_float_to_short(f->no)); data += 3; } } +#else + short4 *data = static_cast(GPU_vertbuf_get_data(vbo.vert_buf)); + + for (int i : tribuf->loops.index_range()) { + BMLoop *l = reinterpret_cast(tribuf->loops[i]); + + short no[3]; + bool smooth = BM_elem_flag_test(l->f, BM_ELEM_SMOOTH); + + if (!smooth) { + normal_float_to_short_v3(no, l->f->no); + *static_cast(GPU_vertbuf_raw_step(&access)) = no; + return; + } + + /* Deal with normal splitting. We do not (yet) implement + * the full autosmoothing functionality here, we just check + * for marked sharp edges. + */ + float3 fno = {}; + + /* Find start of normal span. */ + BMLoop *l2 = l; + do { + l2 = l2->prev; + + if (l2->radial_next == l2 || !(l2->e->head.hflag & BM_ELEM_SMOOTH)) { + break; + } + + l2 = l2->radial_next; + + if (l2->v != l->v) { + l2 = l2->next->next; + } + } while (l2 != l); + + /* No split edges. */ + if (l2 == l) { + normal_float_to_short_v3(no, l->v->no); + *static_cast(GPU_vertbuf_raw_step(&access)) = no; + return; + } + + /* Iterate over normal span */ + int j = 0; + while (1) { + fno += l2->f->no; + + if (j++ > 1000) { + printf("%s: infinite loop error.\n", __func__); + break; + } + + l2 = l2->v == l->v ? l2->prev : l2->next; + l2 = l2->radial_next; + + if (l2 == l2->radial_next || !(l2->e->head.hflag & BM_ELEM_SMOOTH)) { + break; + } + } + normalize_v3(fno); + + normal_float_to_short_v3(no, fno); + *data = no; + data++; + } +#endif break; } case CustomRequest::Mask: { @@ -879,17 +950,19 @@ struct PBVHBatches { if (cd_offset != -1) { float *data = static_cast(GPU_vertbuf_get_data(vbo.vert_buf)); - for (const BMFace *f : *args.bm_faces) { + for (const PBVHTri &tri : tribuf->tris) { + BMFace *f = reinterpret_cast(tri.f.i); if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { continue; } - const BMLoop *l = f->l_first; - *data = bmesh_cd_vert_get(*l->prev->v, cd_offset); - data++; - *data = bmesh_cd_vert_get(*l->v, cd_offset); - data++; - *data = bmesh_cd_vert_get(*l->next->v, cd_offset); - data++; + + BMVert *v1 = reinterpret_cast(tribuf->verts[tri.v[0]].i); + BMVert *v2 = reinterpret_cast(tribuf->verts[tri.v[1]].i); + BMVert *v3 = reinterpret_cast(tribuf->verts[tri.v[2]].i); + + *data++ = bmesh_cd_vert_get(*v1, cd_offset); + *data++ = bmesh_cd_vert_get(*v2, cd_offset); + *data++ = bmesh_cd_vert_get(*v3, cd_offset); } } else { @@ -905,7 +978,8 @@ struct PBVHBatches { uchar4 *data = static_cast(GPU_vertbuf_get_data(vbo.vert_buf)); if (cd_offset != -1) { - for (const BMFace *f : *args.bm_faces) { + for (const PBVHTri &tri : tribuf->tris) { + BMFace *f = reinterpret_cast(tri.f.i); if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { continue; } diff --git a/source/blender/draw/intern/draw_pbvh.h b/source/blender/draw/intern/draw_pbvh.h new file mode 100644 index 00000000000..69097677d98 --- /dev/null +++ b/source/blender/draw/intern/draw_pbvh.h @@ -0,0 +1,26 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "DNA_customdata_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct PBVHBatches; + +enum { + CD_PBVH_CO_TYPE = CD_NUMTYPES, + CD_PBVH_NO_TYPE = CD_NUMTYPES + 1, + CD_PBVH_FSET_TYPE = CD_NUMTYPES + 2, + CD_PBVH_MASK_TYPE = CD_NUMTYPES + 3, +}; + +int drw_pbvh_material_index_get(struct PBVHBatches *batches); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/editors/geometry/geometry_attributes.cc b/source/blender/editors/geometry/geometry_attributes.cc index 5244da22353..c180c9cb4c6 100644 --- a/source/blender/editors/geometry/geometry_attributes.cc +++ b/source/blender/editors/geometry/geometry_attributes.cc @@ -44,6 +44,7 @@ #include "ED_geometry.hh" #include "ED_mesh.hh" #include "ED_object.hh" +#include "ED_sculpt.hh" #include "geometry_intern.hh" @@ -228,6 +229,10 @@ static int geometry_attribute_add_exec(bContext *C, wmOperator *op) Object *ob = ED_object_context(C); ID *id = static_cast(ob->data); + if (ob->type == OB_MESH && ob->mode & OB_MODE_SCULPT && ob->sculpt && ob->sculpt->pbvh) { + sculpt_paint::undo::geometry_begin(ob, op); + } + char name[MAX_NAME]; RNA_string_get(op->ptr, "name", name); eCustomDataType type = (eCustomDataType)RNA_enum_get(op->ptr, "data_type"); @@ -235,11 +240,18 @@ static int geometry_attribute_add_exec(bContext *C, wmOperator *op) CustomDataLayer *layer = BKE_id_attribute_new(id, name, type, domain, op->reports); if (layer == nullptr) { + sculpt_paint::undo::geometry_end(ob); return OPERATOR_CANCELLED; } BKE_id_attributes_active_set(id, layer->name); + if (ob->type == OB_MESH && ob->mode == OB_MODE_SCULPT && ob->sculpt && ob->sculpt->pbvh) { + /* Push attribute into sculpt mesh. */ + BKE_sculptsession_sync_attributes(ob, static_cast(ob->data), false); + sculpt_paint::undo::geometry_end(ob); + } + DEG_id_tag_update(id, ID_RECALC_GEOMETRY); WM_main_add_notifier(NC_GEOM | ND_DATA, id); @@ -304,6 +316,10 @@ static int geometry_attribute_remove_exec(bContext *C, wmOperator *op) ID *id = static_cast(ob->data); CustomDataLayer *layer = BKE_id_attributes_active_get(id); + if (ob->type == OB_MESH && ob->mode & OB_MODE_SCULPT && ob->sculpt && ob->sculpt->pbvh) { + sculpt_paint::undo::geometry_end(ob); + } + if (!BKE_id_attribute_remove(id, layer->name, op->reports)) { return OPERATOR_CANCELLED; } @@ -313,6 +329,12 @@ static int geometry_attribute_remove_exec(bContext *C, wmOperator *op) *active_index -= 1; } + if (ob->type == OB_MESH && ob->mode == OB_MODE_SCULPT && ob->sculpt && ob->sculpt->pbvh) { + /* Push attribute into sculpt mesh. */ + BKE_sculptsession_sync_attributes(ob, static_cast(ob->data), false); + sculpt_paint::undo::geometry_end(ob); + } + DEG_id_tag_update(id, ID_RECALC_GEOMETRY); WM_main_add_notifier(NC_GEOM | ND_DATA, id); @@ -338,6 +360,11 @@ static int geometry_color_attribute_add_exec(bContext *C, wmOperator *op) { Object *ob = ED_object_context(C); ID *id = static_cast(ob->data); + bool is_sculpt = ob->sculpt && ob->mode == OB_MODE_SCULPT && ob->sculpt->pbvh; + + if (is_sculpt) { + sculpt_paint::undo::geometry_begin(ob, op); + } char name[MAX_NAME]; RNA_string_get(op->ptr, "name", name); @@ -349,6 +376,7 @@ static int geometry_color_attribute_add_exec(bContext *C, wmOperator *op) RNA_float_get_array(op->ptr, "color", color); if (layer == nullptr) { + sculpt_paint::undo::geometry_end(ob); return OPERATOR_CANCELLED; } @@ -360,6 +388,10 @@ static int geometry_color_attribute_add_exec(bContext *C, wmOperator *op) BKE_object_attributes_active_color_fill(ob, color, false); + if (is_sculpt) { + sculpt_paint::undo::geometry_end(ob); + } + DEG_id_tag_update(id, ID_RECALC_GEOMETRY); WM_main_add_notifier(NC_GEOM | ND_DATA, id); diff --git a/source/blender/editors/geometry/geometry_intern.hh b/source/blender/editors/geometry/geometry_intern.hh index 145396cfc35..6c18eef2874 100644 --- a/source/blender/editors/geometry/geometry_intern.hh +++ b/source/blender/editors/geometry/geometry_intern.hh @@ -13,6 +13,7 @@ struct wmOperatorType; namespace blender::ed::geometry { /* *** geometry_attributes.cc *** */ + void GEOMETRY_OT_attribute_add(wmOperatorType *ot); void GEOMETRY_OT_attribute_remove(wmOperatorType *ot); void GEOMETRY_OT_color_attribute_add(wmOperatorType *ot); diff --git a/source/blender/editors/include/ED_object.hh b/source/blender/editors/include/ED_object.hh index 69d3bfc8e7c..c8946ce0c44 100644 --- a/source/blender/editors/include/ED_object.hh +++ b/source/blender/editors/include/ED_object.hh @@ -313,8 +313,12 @@ void ED_object_sculptmode_enter_ex(Main *bmain, Scene *scene, Object *ob, bool force_dyntopo, - ReportList *reports); -void ED_object_sculptmode_enter(bContext *C, Depsgraph *depsgraph, ReportList *reports); + ReportList *reports, + bool do_undo); +void ED_object_sculptmode_enter(bContext *C, + Depsgraph *depsgraph, + ReportList *reports, + bool do_undo); void ED_object_sculptmode_exit_ex(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob); void ED_object_sculptmode_exit(bContext *C, Depsgraph *depsgraph); diff --git a/source/blender/editors/mesh/editmesh_mask_extract.cc b/source/blender/editors/mesh/editmesh_mask_extract.cc index 0e284409f74..31220243a2b 100644 --- a/source/blender/editors/mesh/editmesh_mask_extract.cc +++ b/source/blender/editors/mesh/editmesh_mask_extract.cc @@ -7,6 +7,7 @@ */ #include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" #include "DNA_modifier_types.h" #include "DNA_object_types.h" @@ -42,14 +43,12 @@ #include "mesh_intern.hh" /* own include */ +#include "../sculpt_paint/sculpt_intern.hh" + static bool geometry_extract_poll(bContext *C) { Object *ob = CTX_data_active_object(C); if (ob != nullptr && ob->mode == OB_MODE_SCULPT) { - if (ob->sculpt->bm) { - CTX_wm_operator_poll_msg_set(C, "The geometry cannot be extracted with dyntopo activated"); - return false; - } return ED_operator_object_active_editable_mesh(C); } return false; @@ -82,6 +81,13 @@ static int geometry_extract_apply(bContext *C, View3D *v3d = CTX_wm_view3d(C); Scene *scene = CTX_data_scene(C); Depsgraph *depsgraph = CTX_data_depsgraph_on_load(C); + bool use_sculpt_bmesh = ob->mode == OB_MODE_SCULPT && ob->sculpt && ob->sculpt->bm; + BMesh *bm; + + if (use_sculpt_bmesh) { + bm = BM_mesh_copy(ob->sculpt->bm); + BM_mesh_toolflags_set(bm, true); + } ED_object_sculptmode_exit(C, depsgraph); @@ -94,15 +100,17 @@ static int geometry_extract_apply(bContext *C, Mesh *mesh = static_cast(ob->data); Mesh *new_mesh = (Mesh *)BKE_id_copy(bmain, &mesh->id); - const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(new_mesh); - BMeshCreateParams bm_create_params{}; - bm_create_params.use_toolflags = true; - BMesh *bm = BM_mesh_create(&allocsize, &bm_create_params); + if (!use_sculpt_bmesh) { + const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(new_mesh); + BMeshCreateParams bm_create_params{}; + bm_create_params.use_toolflags = true; + bm = BM_mesh_create(&allocsize, &bm_create_params); - BMeshFromMeshParams mesh_to_bm_params{}; - mesh_to_bm_params.calc_face_normal = true; - mesh_to_bm_params.calc_vert_normal = true; - BM_mesh_bm_from_me(bm, new_mesh, &mesh_to_bm_params); + BMeshFromMeshParams mesh_to_bm_params{}; + mesh_to_bm_params.calc_face_normal = true; + mesh_to_bm_params.calc_vert_normal = true; + BM_mesh_bm_from_me(bm, new_mesh, &mesh_to_bm_params); + } BMEditMesh *em = BKE_editmesh_create(bm); @@ -470,21 +478,36 @@ static int paint_mask_slice_exec(bContext *C, wmOperator *op) sculpt_paint::undo::geometry_begin(ob, op); } + BMesh *bm; + const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(new_mesh); BMeshCreateParams bm_create_params{}; bm_create_params.use_toolflags = true; - BMesh *bm = BM_mesh_create(&allocsize, &bm_create_params); - BMeshFromMeshParams mesh_to_bm_params{}; mesh_to_bm_params.calc_face_normal = true; - BM_mesh_bm_from_me(bm, new_mesh, &mesh_to_bm_params); + + if (ob->sculpt && ob->sculpt->bm) { + bm = ob->sculpt->bm; + BM_mesh_elem_toolflags_ensure(bm); + } + else { + bm = BM_mesh_create(&allocsize, &bm_create_params); + + BM_mesh_bm_from_me(bm, new_mesh, &mesh_to_bm_params); + } slice_paint_mask(bm, false, fill_holes, mask_threshold); BKE_id_free(bmain, new_mesh); BMeshToMeshParams bm_to_mesh_params{}; bm_to_mesh_params.calc_object_remap = false; new_mesh = BKE_mesh_from_bmesh_nomain(bm, &bm_to_mesh_params, mesh); - BM_mesh_free(bm); + + if (!ob->sculpt || !ob->sculpt->bm) { + BM_mesh_free(bm); + } + else { + BM_mesh_elem_toolflags_clear(bm); + } if (create_new_object) { ushort local_view_bits = 0; @@ -521,16 +544,67 @@ static int paint_mask_slice_exec(bContext *C, wmOperator *op) BKE_mesh_nomain_to_mesh(new_mesh, mesh, ob); if (ob->mode == OB_MODE_SCULPT) { - if (mesh->attributes().contains(".sculpt_face_set")) { - /* Assign a new Face Set ID to the new faces created by the slice operation. */ - const int next_face_set_id = sculpt_paint::face_set::find_next_available_id(*ob); - sculpt_paint::face_set::initialize_none_to_id(mesh, next_face_set_id); + SculptSession *ss = ob->sculpt; + BKE_sculptsession_update_attr_refs(ob); + + /* Assign a new Face Set ID to the new faces created by the slice operation. */ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_GRIDS: + case PBVH_FACES: + if (mesh->attributes().contains(".sculpt_face_set")) { + /* Assign a new Face Set ID to the new faces created by the slice operation. */ + const int next_face_set_id = sculpt_paint::face_set::find_next_available_id(*ob); + sculpt_paint::face_set::initialize_none_to_id(mesh, next_face_set_id); + } + break; + case PBVH_BMESH: { + const int cd_fset = CustomData_get_offset_named( + &ss->bm->pdata, CD_PROP_INT32, ".sculpt_face_set"); + const int cd_boundary_flag = CustomData_get_offset_named( + &ss->bm->vdata, CD_PROP_INT32, SCULPT_ATTRIBUTE_NAME(boundary_flags)); + const int cd_flag = CustomData_get_offset_named( + &ss->bm->vdata, CD_PROP_INT8, SCULPT_ATTRIBUTE_NAME(flags)); + + if (ss->bm && cd_fset != -1) { + BMFace *f; + BMVert *v; + BMIter iter; + + const int next_face_set_id = sculpt_paint::face_set::find_next_available_id(*ob); + + const int updateflag = SCULPTFLAG_NEED_VALENCE | SCULPTFLAG_NEED_TRIANGULATE; + + BM_ITER_MESH (v, &iter, ss->bm, BM_VERTS_OF_MESH) { + if (cd_boundary_flag != -1) { + int *flag = (int *)BM_ELEM_CD_GET_VOID_P(v, cd_boundary_flag); + *flag |= SCULPT_BOUNDARY_NEEDS_UPDATE; + } + + if (cd_flag != -1) { + *BM_ELEM_CD_PTR(v, cd_flag) |= updateflag; + } + } + + BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { + int fset = BM_ELEM_CD_GET_INT(f, cd_fset); + + if (fset == SCULPT_FACE_SET_NONE) { + BM_ELEM_CD_SET_INT(f, cd_fset, next_face_set_id); + } + } + } + break; + } } + + ss->needs_pbvh_rebuild = true; if (!create_new_object) { sculpt_paint::undo::geometry_end(ob); } } + sculpt_paint::undo::geometry_end(ob); + BKE_mesh_batch_cache_dirty_tag(mesh, BKE_MESH_BATCH_DIRTY_ALL); DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GEOM | ND_DATA, mesh); diff --git a/source/blender/editors/mesh/editmesh_tools.cc b/source/blender/editors/mesh/editmesh_tools.cc index f557b2f9966..000e7406eb5 100644 --- a/source/blender/editors/mesh/editmesh_tools.cc +++ b/source/blender/editors/mesh/editmesh_tools.cc @@ -4384,10 +4384,11 @@ static Base *mesh_separate_tagged( BMeshCreateParams create_params{}; create_params.use_toolflags = true; BMesh *bm_new = BM_mesh_create(&bm_mesh_allocsize_default, &create_params); - BM_mesh_elem_toolflags_ensure(bm_new); /* Needed for 'duplicate' BMO. */ BM_mesh_copy_init_customdata(bm_new, bm_old, &bm_mesh_allocsize_default); + BM_mesh_elem_toolflags_ensure(bm_new); /* Needed for 'duplicate' BMO. */ + /* Take into account user preferences for duplicating actions. */ const eDupli_ID_Flags dupflag = eDupli_ID_Flags(USER_DUP_MESH | (U.dupflag & USER_DUP_ACT)); Base *base_new = ED_object_add_duplicate(bmain, scene, view_layer, base_old, dupflag); diff --git a/source/blender/editors/mesh/editmesh_utils.cc b/source/blender/editors/mesh/editmesh_utils.cc index d0c95460ed8..69eec66bba3 100644 --- a/source/blender/editors/mesh/editmesh_utils.cc +++ b/source/blender/editors/mesh/editmesh_utils.cc @@ -25,6 +25,7 @@ #include "BKE_layer.hh" #include "BKE_mesh.hh" #include "BKE_mesh_mapping.hh" +#include "BKE_pbvh_api.hh" #include "BKE_report.hh" #include "DEG_depsgraph.hh" diff --git a/source/blender/editors/mesh/mesh_data.cc b/source/blender/editors/mesh/mesh_data.cc index a359a80d026..80036bc6281 100644 --- a/source/blender/editors/mesh/mesh_data.cc +++ b/source/blender/editors/mesh/mesh_data.cc @@ -22,6 +22,8 @@ #include "BKE_key.hh" #include "BKE_mesh.hh" #include "BKE_mesh_runtime.hh" +#include "BKE_object.hh" +#include "BKE_paint.hh" #include "BKE_report.hh" #include "DEG_depsgraph.hh" @@ -37,6 +39,9 @@ #include "ED_object.hh" #include "ED_paint.hh" #include "ED_screen.hh" +#include "ED_sculpt.hh" +#include "ED_uvedit.hh" +#include "ED_view3d.hh" #include "GEO_mesh_split_edges.hh" @@ -48,6 +53,8 @@ using blender::float3; using blender::MutableSpan; using blender::Span; +using namespace blender::ed; + static CustomData *mesh_customdata_get_type(Mesh *mesh, const char htype, int *r_tot) { CustomData *data; @@ -485,7 +492,12 @@ static int mesh_uv_texture_add_exec(bContext *C, wmOperator *op) Object *ob = ED_object_context(C); Mesh *mesh = static_cast(ob->data); + if (ob->mode & OB_MODE_SCULPT && ob->sculpt && ob->sculpt->pbvh) { + sculpt_paint::undo::geometry_begin(ob, op); + } + if (ED_mesh_uv_add(mesh, nullptr, true, true, op->reports) == -1) { + sculpt_paint::undo::geometry_end(ob); return OPERATOR_CANCELLED; } @@ -495,6 +507,12 @@ static int mesh_uv_texture_add_exec(bContext *C, wmOperator *op) WM_event_add_notifier(C, NC_SCENE | ND_TOOLSETTINGS, nullptr); } + if (ob->mode == OB_MODE_SCULPT && ob->sculpt) { + /* Push attribute into the sculpt mesh. */ + BKE_sculptsession_sync_attributes(ob, mesh, false); + sculpt_paint::undo::geometry_end(ob); + } + return OPERATOR_FINISHED; } @@ -518,9 +536,14 @@ static int mesh_uv_texture_remove_exec(bContext *C, wmOperator *op) Object *ob = ED_object_context(C); Mesh *mesh = static_cast(ob->data); + if (ob->mode & OB_MODE_SCULPT && ob->sculpt && ob->sculpt->pbvh) { + sculpt_paint::undo::geometry_begin(ob, op); + } + CustomData *ldata = mesh_customdata_get_type(mesh, BM_LOOP, nullptr); const char *name = CustomData_get_active_layer_name(ldata, CD_PROP_FLOAT2); if (!BKE_id_attribute_remove(&mesh->id, name, op->reports)) { + sculpt_paint::undo::geometry_end(ob); return OPERATOR_CANCELLED; } @@ -530,6 +553,12 @@ static int mesh_uv_texture_remove_exec(bContext *C, wmOperator *op) WM_event_add_notifier(C, NC_SCENE | ND_TOOLSETTINGS, nullptr); } + if (ob->mode == OB_MODE_SCULPT && ob->sculpt) { + /* Delete attribute from the sculpt mesh. */ + BKE_sculptsession_sync_attributes(ob, mesh, false); + sculpt_paint::undo::geometry_end(ob); + } + DEG_id_tag_update(&mesh->id, ID_RECALC_GEOMETRY); WM_main_add_notifier(NC_GEOM | ND_DATA, mesh); diff --git a/source/blender/editors/mesh/mesh_intern.h b/source/blender/editors/mesh/mesh_intern.h new file mode 100644 index 00000000000..8f2e0ade860 --- /dev/null +++ b/source/blender/editors/mesh/mesh_intern.h @@ -0,0 +1,333 @@ +/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edmesh + */ + +/* Internal for editmesh_xxxx.c functions */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +struct BMEditMesh; +struct BMElem; +struct BMOperator; +struct EnumPropertyItem; +struct LinkNode; +struct Object; +struct bContext; +struct wmKeyConfig; +struct wmKeyMap; +struct wmOperator; +struct wmOperatorType; + +/* *** editmesh_utils.cc *** */ + +/* + * ok: the EDBM module is for editmode bmesh stuff. in contrast, the + * BMEdit module is for code shared with blenkernel that concerns + * the BMEditMesh structure. */ + +/** Calls a bmesh op, reporting errors to the user, etc. */ +bool EDBM_op_callf(struct BMEditMesh *em, struct wmOperator *op, const char *fmt, ...); +bool EDBM_op_call_and_selectf(struct BMEditMesh *em, + struct wmOperator *op, + const char *select_slot, + bool select_replace, + const char *fmt, + ...); +/** + * Same as above, but doesn't report errors. + */ +bool EDBM_op_call_silentf(struct BMEditMesh *em, const char *fmt, ...); + +/** + * These next two functions are the split version of EDBM_op_callf, so you can + * do stuff with a bmesh operator, after initializing it but before executing it. + * + * execute the operator with #BMO_op_exec. + */ +bool EDBM_op_init( + struct BMEditMesh *em, struct BMOperator *bmop, struct wmOperator *op, const char *fmt, ...); + +/** + * Cleans up after a bmesh operator. + * + * The return value: + * - False on error (the mesh must not be changed). + * - True on success, executes and finishes a #BMesh operator. + */ +bool EDBM_op_finish(struct BMEditMesh *em, + struct BMOperator *bmop, + struct wmOperator *op, + bool do_report); + +void EDBM_stats_update(struct BMEditMesh *em); + +/** + * Poll call for mesh operators requiring a view3d context. + */ +bool EDBM_view3d_poll(struct bContext *C); + +struct BMElem *EDBM_elem_from_selectmode(struct BMEditMesh *em, + struct BMVert *eve, + struct BMEdge *eed, + struct BMFace *efa); + +/** + * Used when we want to store a single index for any vert/edge/face. + * + * Intended for use with operators. + */ +int EDBM_elem_to_index_any(struct BMEditMesh *em, struct BMElem *ele); +struct BMElem *EDBM_elem_from_index_any(struct BMEditMesh *em, uint index); + +int EDBM_elem_to_index_any_multi(const struct Scene *scene, + struct ViewLayer *view_layer, + struct BMEditMesh *em, + struct BMElem *ele, + int *r_object_index); +struct BMElem *EDBM_elem_from_index_any_multi(const struct Scene *scene, + struct ViewLayer *view_layer, + uint object_index, + uint elem_index, + struct Object **r_obedit); + +/** + * Extrudes individual edges. + */ +bool edbm_extrude_edges_indiv(struct BMEditMesh *em, + struct wmOperator *op, + char hflag, + bool use_normal_flip); + +/* *** `editmesh_add.cc` *** */ + +void MESH_OT_primitive_plane_add(struct wmOperatorType *ot); +void MESH_OT_primitive_cube_add(struct wmOperatorType *ot); +void MESH_OT_primitive_circle_add(struct wmOperatorType *ot); +void MESH_OT_primitive_cylinder_add(struct wmOperatorType *ot); +void MESH_OT_primitive_cone_add(struct wmOperatorType *ot); +void MESH_OT_primitive_grid_add(struct wmOperatorType *ot); +void MESH_OT_primitive_monkey_add(struct wmOperatorType *ot); +void MESH_OT_primitive_uv_sphere_add(struct wmOperatorType *ot); +void MESH_OT_primitive_ico_sphere_add(struct wmOperatorType *ot); + +/* *** `editmesh_add_gizmo.cc` *** */ + +void MESH_OT_primitive_cube_add_gizmo(struct wmOperatorType *ot); + +/* *** editmesh_attribute.cc *** */ + +void MESH_OT_attribute_set(struct wmOperatorType *ot); + +/* *** `editmesh_bevel.cc` *** */ + +void MESH_OT_bevel(struct wmOperatorType *ot); +struct wmKeyMap *bevel_modal_keymap(struct wmKeyConfig *keyconf); + +/* *** `editmesh_bisect.cc` *** */ + +void MESH_OT_bisect(struct wmOperatorType *ot); + +/* *** `editmesh_extrude.cc` *** */ + +void MESH_OT_extrude_repeat(struct wmOperatorType *ot); +void MESH_OT_extrude_region(struct wmOperatorType *ot); +void MESH_OT_extrude_context(struct wmOperatorType *ot); +void MESH_OT_extrude_verts_indiv(struct wmOperatorType *ot); +void MESH_OT_extrude_edges_indiv(struct wmOperatorType *ot); +void MESH_OT_extrude_faces_indiv(struct wmOperatorType *ot); +void MESH_OT_dupli_extrude_cursor(struct wmOperatorType *ot); + +/* *** `editmesh_extrude_screw.cc` *** */ + +void MESH_OT_screw(struct wmOperatorType *ot); + +/* *** `editmesh_extrude_spin.cc` *** */ + +void MESH_OT_spin(struct wmOperatorType *ot); + +/* *** `editmesh_extrude_spin_gizmo.cc` *** */ + +void MESH_GGT_spin(struct wmGizmoGroupType *gzgt); +void MESH_GGT_spin_redo(struct wmGizmoGroupType *gzgt); + +/* *** `editmesh_polybuild.cc` *** */ + +void MESH_OT_polybuild_face_at_cursor(struct wmOperatorType *ot); +void MESH_OT_polybuild_split_at_cursor(struct wmOperatorType *ot); +void MESH_OT_polybuild_dissolve_at_cursor(struct wmOperatorType *ot); +void MESH_OT_polybuild_transform_at_cursor(struct wmOperatorType *ot); +void MESH_OT_polybuild_delete_at_cursor(struct wmOperatorType *ot); + +/* *** `editmesh_inset.cc` *** */ + +void MESH_OT_inset(struct wmOperatorType *ot); + +/* *** `editmesh_intersect.cc` *** */ + +void MESH_OT_intersect(struct wmOperatorType *ot); +void MESH_OT_intersect_boolean(struct wmOperatorType *ot); +void MESH_OT_face_split_by_edges(struct wmOperatorType *ot); + +/* *** `editmesh_knife.cc` *** */ + +void MESH_OT_knife_tool(struct wmOperatorType *ot); +void MESH_OT_knife_project(struct wmOperatorType *ot); +/** + * \param use_tag: When set, tag all faces inside the polylines. + */ +void EDBM_mesh_knife(struct ViewContext *vc, + struct Object **objects, + int objects_len, + struct LinkNode *polys, + bool use_tag, + bool cut_through); + +struct wmKeyMap *knifetool_modal_keymap(struct wmKeyConfig *keyconf); + +/* *** `editmesh_loopcut.cc` *** */ + +void MESH_OT_loopcut(struct wmOperatorType *ot); + +/* *** `editmesh_rip.cc` *** */ + +void MESH_OT_rip(struct wmOperatorType *ot); +void MESH_OT_rip_edge(struct wmOperatorType *ot); + +/* *** editmesh_select.cc *** */ + +void MESH_OT_select_similar(struct wmOperatorType *ot); +void MESH_OT_select_similar_region(struct wmOperatorType *ot); +void MESH_OT_select_mode(struct wmOperatorType *ot); +void MESH_OT_loop_multi_select(struct wmOperatorType *ot); +void MESH_OT_loop_select(struct wmOperatorType *ot); +void MESH_OT_edgering_select(struct wmOperatorType *ot); +void MESH_OT_select_all(struct wmOperatorType *ot); +void MESH_OT_select_interior_faces(struct wmOperatorType *ot); +void MESH_OT_shortest_path_pick(struct wmOperatorType *ot); +void MESH_OT_select_linked(struct wmOperatorType *ot); +void MESH_OT_select_linked_pick(struct wmOperatorType *ot); +void MESH_OT_select_face_by_sides(struct wmOperatorType *ot); +void MESH_OT_select_loose(struct wmOperatorType *ot); +void MESH_OT_select_mirror(struct wmOperatorType *ot); +void MESH_OT_select_more(struct wmOperatorType *ot); +void MESH_OT_select_less(struct wmOperatorType *ot); +void MESH_OT_select_nth(struct wmOperatorType *ot); +void MESH_OT_edges_select_sharp(struct wmOperatorType *ot); +void MESH_OT_faces_select_linked_flat(struct wmOperatorType *ot); +void MESH_OT_select_non_manifold(struct wmOperatorType *ot); +void MESH_OT_select_random(struct wmOperatorType *ot); +void MESH_OT_select_ungrouped(struct wmOperatorType *ot); +void MESH_OT_select_axis(struct wmOperatorType *ot); +void MESH_OT_region_to_loop(struct wmOperatorType *ot); +void MESH_OT_loop_to_region(struct wmOperatorType *ot); +void MESH_OT_select_by_attribute(struct wmOperatorType *ot); +void MESH_OT_shortest_path_select(struct wmOperatorType *ot); + +extern struct EnumPropertyItem *corner_type_items; + +/* *** editmesh_tools.cc *** */ +void MESH_OT_subdivide(struct wmOperatorType *ot); +void MESH_OT_subdivide_edgering(struct wmOperatorType *ot); +void MESH_OT_unsubdivide(struct wmOperatorType *ot); +void MESH_OT_normals_make_consistent(struct wmOperatorType *ot); +void MESH_OT_vertices_smooth(struct wmOperatorType *ot); +void MESH_OT_vertices_smooth_laplacian(struct wmOperatorType *ot); +void MESH_OT_vert_connect(struct wmOperatorType *ot); +void MESH_OT_vert_connect_path(struct wmOperatorType *ot); +void MESH_OT_vert_connect_concave(struct wmOperatorType *ot); +void MESH_OT_vert_connect_nonplanar(struct wmOperatorType *ot); +void MESH_OT_face_make_planar(struct wmOperatorType *ot); +void MESH_OT_edge_split(struct wmOperatorType *ot); +void MESH_OT_bridge_edge_loops(struct wmOperatorType *ot); +void MESH_OT_offset_edge_loops(struct wmOperatorType *ot); +void MESH_OT_wireframe(struct wmOperatorType *ot); +void MESH_OT_convex_hull(struct wmOperatorType *ot); +void MESH_OT_symmetrize(struct wmOperatorType *ot); +void MESH_OT_symmetry_snap(struct wmOperatorType *ot); +void MESH_OT_shape_propagate_to_all(struct wmOperatorType *ot); +void MESH_OT_blend_from_shape(struct wmOperatorType *ot); +void MESH_OT_sort_elements(struct wmOperatorType *ot); +void MESH_OT_uvs_rotate(struct wmOperatorType *ot); +void MESH_OT_uvs_reverse(struct wmOperatorType *ot); +void MESH_OT_colors_rotate(struct wmOperatorType *ot); +void MESH_OT_colors_reverse(struct wmOperatorType *ot); +void MESH_OT_delete(struct wmOperatorType *ot); +void MESH_OT_delete_loose(struct wmOperatorType *ot); +void MESH_OT_edge_collapse(struct wmOperatorType *ot); +void MESH_OT_faces_shade_smooth(struct wmOperatorType *ot); +void MESH_OT_faces_shade_flat(struct wmOperatorType *ot); +void MESH_OT_split(struct wmOperatorType *ot); +void MESH_OT_edge_rotate(struct wmOperatorType *ot); +void MESH_OT_hide(struct wmOperatorType *ot); +void MESH_OT_reveal(struct wmOperatorType *ot); +void MESH_OT_mark_seam(struct wmOperatorType *ot); +void MESH_OT_mark_sharp(struct wmOperatorType *ot); +void MESH_OT_flip_normals(struct wmOperatorType *ot); +void MESH_OT_solidify(struct wmOperatorType *ot); +void MESH_OT_knife_cut(struct wmOperatorType *ot); +void MESH_OT_separate(struct wmOperatorType *ot); +void MESH_OT_fill(struct wmOperatorType *ot); +void MESH_OT_fill_grid(struct wmOperatorType *ot); +void MESH_OT_fill_holes(struct wmOperatorType *ot); +void MESH_OT_beautify_fill(struct wmOperatorType *ot); +void MESH_OT_quads_convert_to_tris(struct wmOperatorType *ot); +void MESH_OT_tris_convert_to_quads(struct wmOperatorType *ot); +void MESH_OT_decimate(struct wmOperatorType *ot); +void MESH_OT_dissolve_verts(struct wmOperatorType *ot); +void MESH_OT_dissolve_edges(struct wmOperatorType *ot); +void MESH_OT_dissolve_faces(struct wmOperatorType *ot); +void MESH_OT_dissolve_mode(struct wmOperatorType *ot); +void MESH_OT_dissolve_limited(struct wmOperatorType *ot); +void MESH_OT_dissolve_degenerate(struct wmOperatorType *ot); +void MESH_OT_delete_edgeloop(struct wmOperatorType *ot); +void MESH_OT_edge_face_add(struct wmOperatorType *ot); +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_normals(struct wmOperatorType *ot); +void MESH_OT_split_normals(struct wmOperatorType *ot); +void MESH_OT_normals_tools(struct wmOperatorType *ot); +void MESH_OT_set_normals_from_faces(struct wmOperatorType *ot); +void MESH_OT_average_normals(struct wmOperatorType *ot); +void MESH_OT_smooth_normals(struct wmOperatorType *ot); +void MESH_OT_mod_weighted_strength(struct wmOperatorType *ot); +void MESH_OT_mres_test(struct wmOperatorType *ot); +void MESH_OT_flip_quad_tessellation(struct wmOperatorType *ot); + +/* *** editmesh_mask_extract.cc *** */ + +void MESH_OT_paint_mask_extract(struct wmOperatorType *ot); +void MESH_OT_face_set_extract(struct wmOperatorType *ot); +void MESH_OT_paint_mask_slice(struct wmOperatorType *ot); + +/** Called in `transform_ops.cc`, on each regeneration of key-maps. */ +struct wmKeyMap *point_normals_modal_keymap(wmKeyConfig *keyconf); + +#if defined(WITH_FREESTYLE) +void MESH_OT_mark_freestyle_edge(struct wmOperatorType *ot); +void MESH_OT_mark_freestyle_face(struct wmOperatorType *ot); +#endif + +/* *** mesh_data.cc *** */ + +void MESH_OT_uv_texture_add(struct wmOperatorType *ot); +void MESH_OT_uv_texture_remove(struct wmOperatorType *ot); +void MESH_OT_customdata_mask_clear(struct wmOperatorType *ot); +void MESH_OT_customdata_skin_add(struct wmOperatorType *ot); +void MESH_OT_customdata_skin_clear(struct wmOperatorType *ot); +void MESH_OT_customdata_custom_splitnormals_add(struct wmOperatorType *ot); +void MESH_OT_customdata_custom_splitnormals_clear(struct wmOperatorType *ot); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/editors/mesh/meshtools.cc b/source/blender/editors/mesh/meshtools.cc index 1adc3f50061..49b827e6046 100644 --- a/source/blender/editors/mesh/meshtools.cc +++ b/source/blender/editors/mesh/meshtools.cc @@ -67,6 +67,155 @@ using blender::Span; /* join selected meshes into the active mesh, context sensitive * return 0 if no join is made (error) and 1 if the join is done */ +static const char *get_id_name(int elemtype) +{ + switch (elemtype) { + case BM_VERT: + return "vertex_id"; + case BM_EDGE: + return "edge_id"; + case BM_LOOP: + return "loop_id"; + case BM_FACE: + return "face_id"; + default: + return nullptr; + } +} + +static void get_id_range(Mesh *mesh, + CustomData *vdata, + CustomData *edata, + CustomData *ldata, + CustomData *pdata, + int totvert, + int totedge, + int totloop, + int totpoly, + int *r_min, + int *r_max) +{ + const CustomData *datas[4] = {vdata, edata, ldata, pdata}; + int tots[4] = {totvert, totedge, totloop, totpoly}; + int min_id = 0, max_id = 0; + + for (int i = 0; i < 4; i++) { + const int *ids = (const int *)CustomData_get_layer_named( + datas[i], CD_PROP_INT32, get_id_name(1 << i)); + if (!ids) { + continue; + } + + if (!tots[i]) { + continue; + } + + if (i == 0) { + min_id = max_id = *ids; + } + else { + min_id = std::min(min_id, *ids); + max_id = std::max(max_id, *ids); + } + + ids++; + + for (int j = 1; j < tots[i]; j++, ids++) { + min_id = std::min(min_id, *ids); + max_id = std::max(max_id, *ids); + } + } + + *r_min = min_id; + *r_max = max_id; +} + +static void handle_missing_id_layers(Mesh *src, + Mesh *dst, + CustomData *vdata, + CustomData *edata, + CustomData *ldata, + CustomData *pdata, + int totvert, + int totedge, + int totloop, + int totpoly) +{ + const CustomData *src_datas[4] = { + &src->vert_data, &src->edge_data, &src->corner_data, &src->face_data}; + CustomData *dst_datas[4] = {vdata, edata, ldata, pdata}; + int srctots[4] = {src->verts_num, src->edges_num, src->corners_num, src->faces_num}; + int dsttots[4] = {totvert, totedge, totloop, totpoly}; + + // find starting max id in dst + int dst_range[2], src_range[2]; + + get_id_range(src, + &src->vert_data, + &src->edge_data, + &src->corner_data, + &src->face_data, + src->verts_num, + src->edges_num, + src->corners_num, + src->faces_num, + src_range, + src_range + 1); + + get_id_range(dst, + vdata, + edata, + ldata, + pdata, + totvert, + totedge, + totloop, + totpoly, + dst_range, + dst_range + 1); + + for (int i = 0; i < 4; i++) { + const CustomData *srcdata = src_datas[i]; + CustomData *dstdata = dst_datas[i]; + + const char *idname = get_id_name(1 << i); + const bool haveid_src = CustomData_get_layer_named(srcdata, CD_PROP_INT32, idname); + const bool haveid_dst = CustomData_get_layer_named(dstdata, CD_PROP_INT32, idname); + + if (haveid_dst && haveid_src) { + // assign ids + int offset = dst_range[1] - src_range[0] + 1; + + const int *srcids = (const int *)CustomData_get_layer_named(srcdata, CD_PROP_INT32, idname); + int *dstids = (int *)CustomData_get_layer_named_for_write( + dstdata, CD_PROP_INT32, idname, dsttots[i]); + + int start = dsttots[i]; + int end = start + srctots[i]; + + // offset ids + dstids += start; + for (int i = start; i < end; i++, dstids++, srcids++) { + *dstids = (*srcids) + offset; + } + } + else if (haveid_dst) { + int curid = dst_range[1] + 1; + int *dstids = (int *)CustomData_get_layer_named_for_write( + dstdata, CD_PROP_INT32, idname, dsttots[i]); + + int start = dsttots[i]; + int end = start + srctots[i]; + dstids += start; + + // assign new ids + for (int i = start; i < end; i++, dstids++) { + *dstids = curid++; + } + } + } +} + static void join_mesh_single(Depsgraph *depsgraph, Main *bmain, Scene *scene, @@ -278,6 +427,17 @@ static void join_mesh_single(Depsgraph *depsgraph, } } + handle_missing_id_layers(mesh, + (Mesh *)ob_dst->data, + vert_data, + edge_data, + ldata, + face_data, + *vertofs, + *edgeofs, + *loopofs, + *polyofs); + /* these are used for relinking (cannot be set earlier, or else reattaching goes wrong) */ *vertofs += mesh->verts_num; *vert_positions_pp += mesh->verts_num; diff --git a/source/blender/editors/object/object_remesh.cc b/source/blender/editors/object/object_remesh.cc index be7a6488b0d..b2a732920c1 100644 --- a/source/blender/editors/object/object_remesh.cc +++ b/source/blender/editors/object/object_remesh.cc @@ -62,6 +62,8 @@ #include "object_intern.h" /* own include */ +#include "bmesh_idmap.hh" + using blender::float3; using blender::IndexRange; using blender::Span; @@ -91,11 +93,6 @@ static bool object_remesh_poll(bContext *C) return false; } - if (ob->mode == OB_MODE_SCULPT && ob->sculpt->bm) { - CTX_wm_operator_poll_msg_set(C, "The remesher cannot run with dyntopo activated"); - return false; - } - if (BKE_modifiers_uses_multires(ob)) { CTX_wm_operator_poll_msg_set( C, "The remesher cannot run with a Multires modifier in the modifier stack"); @@ -127,6 +124,8 @@ static int voxel_remesh_exec(bContext *C, wmOperator *op) isovalue = mesh->remesh_voxel_size * 0.3f; } + bool is_dyntopo = false; + Mesh *new_mesh = BKE_mesh_remesh_voxel( mesh, mesh->remesh_voxel_size, mesh->remesh_voxel_adaptivity, isovalue); @@ -136,7 +135,16 @@ static int voxel_remesh_exec(bContext *C, wmOperator *op) } if (ob->mode == OB_MODE_SCULPT) { + BKE_sculpt_update_object_for_edit(CTX_data_depsgraph_pointer(C), ob, false); sculpt_paint::undo::geometry_begin(ob, op); + + if (ob->sculpt->bm) { + is_dyntopo = true; + BKE_sculptsession_bm_to_me(ob, false); + + /* Destroy dyntopo IDs so we don't waste time reprojecting them. */ + BM_idmap_clear_attributes_mesh(mesh); + } } if (mesh->flag & ME_REMESH_FIX_POLES && mesh->remesh_voxel_adaptivity <= 0.0f) { @@ -161,6 +169,22 @@ static int voxel_remesh_exec(bContext *C, wmOperator *op) BKE_mesh_nomain_to_mesh(new_mesh, mesh, ob); if (ob->mode == OB_MODE_SCULPT) { + /* Convert mesh back to bmesh. */ + if (is_dyntopo) { + BMesh *bm = BKE_sculptsession_empty_bmesh_create(); + BMeshFromMeshParams params = {0}; + params.calc_face_normal = true; + params.use_shapekey = true; + params.active_shapekey = ob->shapenr; + params.copy_temp_cdlayers = false; + + BM_mesh_bm_from_me(bm, mesh, ¶ms); + BM_mesh_elem_table_ensure(bm, BM_VERT | BM_EDGE | BM_FACE); + + BKE_sculpt_set_bmesh(ob, bm, true); + BKE_sculpt_ensure_idmap(ob); + } + sculpt_paint::undo::geometry_end(ob); } diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index 8977186e460..77a5610ce36 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -64,6 +64,8 @@ set(SRC paint_vertex_weight_utils.cc paint_weight.cc sculpt.cc + sculpt_api.cc + sculpt_curvature.cc sculpt_automasking.cc sculpt_boundary.cc sculpt_brush_types.cc @@ -89,7 +91,6 @@ set(SRC sculpt_trim.cc sculpt_undo.cc sculpt_uv.cc - curves_sculpt_intern.hh grease_pencil_intern.hh paint_intern.hh diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc index bb10caaeb3e..96c76c0c1e5 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc @@ -267,7 +267,7 @@ static void SCULPT_CURVES_OT_brush_stroke(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - paint_stroke_operator_properties(ot); + paint_stroke_operator_properties(ot, false); } /** \} */ diff --git a/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc b/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc index 9526dd83bfd..7673bbe1515 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc @@ -203,7 +203,7 @@ static void GREASE_PENCIL_OT_brush_stroke(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - paint_stroke_operator_properties(ot); + paint_stroke_operator_properties(ot, false); } /** \} */ diff --git a/source/blender/editors/sculpt_paint/paint_cursor.cc b/source/blender/editors/sculpt_paint/paint_cursor.cc index ca9db0a0c16..e6ae2f78072 100644 --- a/source/blender/editors/sculpt_paint/paint_cursor.cc +++ b/source/blender/editors/sculpt_paint/paint_cursor.cc @@ -62,6 +62,9 @@ * removed eventually (TODO) */ #include "sculpt_intern.hh" +using namespace blender; +using namespace blender::ed::sculpt_paint; + /* TODOs: * * Some of the cursor drawing code is doing non-draw stuff @@ -1642,6 +1645,99 @@ static void paint_draw_3D_view_inactive_brush_cursor(PaintCursorContext *pcontex 80); } +static void sculpt_cursor_draw_active_face_set_color_set(PaintCursorContext *pcontext) +{ + + SculptSession *ss = pcontext->ss; + + if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) { + return; + } + + const int active_face_set = face_set::active_face_set_get(ss); + uchar color[4] = {UCHAR_MAX, UCHAR_MAX, UCHAR_MAX, UCHAR_MAX}; + Object *ob = CTX_data_active_object(pcontext->C); + Mesh *mesh = (Mesh *)ob->data; + if (active_face_set != mesh->face_sets_color_default) { + BKE_paint_face_set_overlay_color_get(active_face_set, mesh->face_sets_color_seed, color); + color[3] = UCHAR_MAX; + } + else { + color[3] /= 2; + } + + immUniformColor4ubv(color); +} + +static void sculpt_cursor_draw_3D_face_set_preview(PaintCursorContext *pcontext) +{ + + SculptSession *ss = pcontext->ss; + + if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) { + return; + } + + GPU_line_width(1.0f); + sculpt_cursor_draw_active_face_set_color_set(pcontext); + + /* + MPoly *poly = &ss->mpoly[fi]; + MLoop *loops = ss->mloop; + const int totpoints = poly->totloop; + + immBegin(GPU_PRIM_LINE_STRIP, totpoints + 1); + for (int i = 0; i < totpoints; i++) { + float co[3]; + copy_v3_v3(co, SCULPT_vertex_co_get(ss, loops[poly->loopstart + i].v)); + immVertex3fv(pcontext->pos, co); + } + immVertex3fv(pcontext->pos, SCULPT_vertex_co_get(ss, loops[poly->loopstart].v)); + immEnd(); + */ + + /* + int v_in_poly = 0; + for (int i = 0; i < totpoints; i++) { + if (ss->active_vertex == loops[poly->loopstart + i].v) { + v_in_poly = i; + } + } + const int next_v = v_in_poly == poly->totloop - 1? 0 : v_in_poly + 1; + const int prev_v = v_in_poly == 0? poly->totloop - 1 : v_in_poly - 1; + + + immBegin(GPU_PRIM_LINES, 4); + immVertex3fv(pcontext->pos, SCULPT_vertex_co_get(ss, ss->active_vertex)); + immVertex3fv(pcontext->pos, SCULPT_vertex_co_get(ss, loops[poly->loopstart + next_v].v)); + + + immVertex3fv(pcontext->pos, SCULPT_vertex_co_get(ss, ss->active_vertex)); + immVertex3fv(pcontext->pos, SCULPT_vertex_co_get(ss, loops[poly->loopstart + prev_v].v)); + + immEnd(); + */ + + if (ss->vert_to_face_map.is_empty()) { + return; + } + + int total = 0; + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, ss->active_vertex, ni) { + total++; + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + + immBegin(GPU_PRIM_LINES, total * 2); + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, ss->active_vertex, ni) { + immVertex3fv(pcontext->pos, SCULPT_active_vertex_co_get(ss)); + immVertex3fv(pcontext->pos, SCULPT_vertex_co_get(ss, ni.vertex)); + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + immEnd(); +} + static void paint_cursor_update_object_space_radius(PaintCursorContext *pcontext) { if (!BKE_brush_use_locked_size(pcontext->scene, pcontext->brush)) { @@ -1789,6 +1885,7 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext * } if (len_v3v3(active_vertex_co, pcontext->location) < pcontext->radius) { immUniformColor3fvAlpha(pcontext->outline_col, pcontext->outline_alpha); + sculpt_cursor_draw_active_face_set_color_set(pcontext); cursor_draw_point_with_symmetry(pcontext->pos, pcontext->region, active_vertex_co, @@ -1833,6 +1930,15 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext * 2); } + /* Transform Pivot. */ + if (pcontext->paint && pcontext->paint->flags & PAINT_SCULPT_SHOW_PIVOT) { + cursor_draw_point_screen_space(pcontext->pos, + pcontext->region, + pcontext->ss->pivot_pos, + pcontext->vc.obact->object_to_world().ptr(), + 2); + } + if (is_brush_tool && brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) { paint_cursor_preview_boundary_data_update(pcontext, update_previews); paint_cursor_preview_boundary_data_pivot_draw(pcontext); @@ -1872,6 +1978,9 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext * boundary::pivot_line_preview_draw(pcontext->pos, pcontext->ss); } + /* Face Set Preview. */ + sculpt_cursor_draw_3D_face_set_preview(pcontext); + GPU_matrix_pop(); /* Drawing Cursor overlays in Paint Cursor space (as additional info on top of the brush cursor) @@ -2036,8 +2145,10 @@ static void paint_cursor_update_rake_rotation(PaintCursorContext *pcontext) * and we may get interference with the stroke itself. * For line strokes, such interference is visible. */ if (!pcontext->ups->stroke_active) { + float zero[2] = {0.0f, 0.0f}; + paint_calculate_rake_rotation( - pcontext->ups, pcontext->brush, pcontext->translation, pcontext->mode, true); + pcontext->ups, pcontext->brush, pcontext->translation, zero, pcontext->mode, true); } } diff --git a/source/blender/editors/sculpt_paint/paint_hide.cc b/source/blender/editors/sculpt_paint/paint_hide.cc index 7eeef2a4e43..30b2dc5d45c 100644 --- a/source/blender/editors/sculpt_paint/paint_hide.cc +++ b/source/blender/editors/sculpt_paint/paint_hide.cc @@ -49,6 +49,8 @@ /* For undo push. */ #include "sculpt_intern.hh" +using blender::Vector; +using blender::bke::dyntopo::DyntopoSet; namespace blender::ed::sculpt_paint::hide { void sync_all_from_faces(Object &object) @@ -355,24 +357,6 @@ static void grid_hide_update(Depsgraph &depsgraph, } } -static void partialvis_all_update_grids(Depsgraph &depsgraph, - Object &object, - const VisAction action, - const Span nodes) -{ - switch (action) { - case VisAction::Hide: - grid_hide_update(depsgraph, - object, - nodes, - [&](const int /*verts*/, MutableBoundedBitSpan hide) { hide.fill(true); }); - break; - case VisAction::Show: - grids_show_all(depsgraph, object, nodes); - break; - } -} - static void partialvis_masked_update_grids(Depsgraph &depsgraph, Object &object, const VisAction action, @@ -434,8 +418,25 @@ static void partialvis_gesture_update_grids(Depsgraph &depsgraph, } }); } +static void partialvis_all_update_grids(Depsgraph &depsgraph, + Object &object, + const VisAction action, + const Span nodes) +{ + switch (action) { + case VisAction::Hide: + grid_hide_update(depsgraph, + object, + nodes, + [&](const int /*verts*/, MutableBoundedBitSpan hide) { hide.fill(true); }); + break; + case VisAction::Show: + grids_show_all(depsgraph, object, nodes); + break; + } +} -static void partialvis_update_bmesh_verts(const Set &verts, +static void partialvis_update_bmesh_verts(DyntopoSet &verts, const VisAction action, const FunctionRef should_update, bool *any_changed, @@ -458,10 +459,17 @@ static void partialvis_update_bmesh_verts(const Set &verts, } } -static void partialvis_update_bmesh_faces(const Set &faces) +static void partialvis_update_bmesh_faces(DyntopoSet &faces) { for (BMFace *f : faces) { - if (paint_is_bmesh_face_hidden(f)) { + bool hidden = false; + + BMLoop *l = f->l_first; + do { + hidden |= bool(BM_elem_flag_test(l->v, BM_ELEM_HIDDEN)); + } while ((l = l->next) != f->l_first); + + if (hidden) { BM_elem_flag_enable(f, BM_ELEM_HIDDEN); } else { @@ -475,17 +483,19 @@ static void partialvis_update_bmesh_nodes(Object *ob, const VisAction action, const FunctionRef vert_test_fn) { + DyntopoSet *unique, *other; + for (PBVHNode *node : nodes) { + unique = &BKE_pbvh_bmesh_node_unique_verts(node); + other = &BKE_pbvh_bmesh_node_other_verts(node); + bool any_changed = false; bool any_visible = false; undo::push_node(ob, node, undo::Type::HideVert); - partialvis_update_bmesh_verts( - BKE_pbvh_bmesh_node_unique_verts(node), action, vert_test_fn, &any_changed, &any_visible); - - partialvis_update_bmesh_verts( - BKE_pbvh_bmesh_node_other_verts(node), action, vert_test_fn, &any_changed, &any_visible); + partialvis_update_bmesh_verts(*unique, action, vert_test_fn, &any_changed, &any_visible); + partialvis_update_bmesh_verts(*other, action, vert_test_fn, &any_changed, &any_visible); /* Finally loop over node faces and tag the ones that are fully hidden. */ partialvis_update_bmesh_faces(BKE_pbvh_bmesh_node_faces(node)); @@ -493,6 +503,7 @@ static void partialvis_update_bmesh_nodes(Object *ob, if (any_changed) { BKE_pbvh_node_mark_rebuild_draw(node); BKE_pbvh_node_fully_hidden_set(node, !any_visible); + BKE_pbvh_vert_tag_update_normal_triangulation(node); } } } @@ -586,7 +597,7 @@ static int hide_show_all_exec(bContext *C, wmOperator *op) const VisAction action = VisAction(RNA_enum_get(op->ptr, "action")); PBVH *pbvh = BKE_sculpt_object_pbvh_ensure(depsgraph, ob); - BLI_assert(BKE_object_sculpt_pbvh_get(ob) == pbvh); + BKE_sculpt_hide_poly_ensure(ob); /* Start undo. */ switch (action) { diff --git a/source/blender/editors/sculpt_paint/paint_image_ops_paint.cc b/source/blender/editors/sculpt_paint/paint_image_ops_paint.cc index 90221c3ea22..e8ee7da0017 100644 --- a/source/blender/editors/sculpt_paint/paint_image_ops_paint.cc +++ b/source/blender/editors/sculpt_paint/paint_image_ops_paint.cc @@ -524,5 +524,5 @@ void PAINT_OT_image_paint(wmOperatorType *ot) /* flags */ ot->flag = OPTYPE_BLOCKING; - paint_stroke_operator_properties(ot); + paint_stroke_operator_properties(ot, false); } diff --git a/source/blender/editors/sculpt_paint/paint_image_proj.cc b/source/blender/editors/sculpt_paint/paint_image_proj.cc index 4dca4c5793d..c656d0746ae 100644 --- a/source/blender/editors/sculpt_paint/paint_image_proj.cc +++ b/source/blender/editors/sculpt_paint/paint_image_proj.cc @@ -1060,10 +1060,11 @@ static bool pixel_bounds_uv(const float uv_quad[4][2], #endif static bool pixel_bounds_array( - float (*uv)[2], rcti *bounds_px, const int ibuf_x, const int ibuf_y, int tot) + float (*in_uv)[2], rcti *bounds_px, const int ibuf_x, const int ibuf_y, int tot) { /* UV bounds */ float min_uv[2], max_uv[2]; + float(*uv)[2] = in_uv; if (tot == 0) { return false; @@ -1071,9 +1072,8 @@ static bool pixel_bounds_array( INIT_MINMAX2(min_uv, max_uv); - while (tot--) { + for (int i = 0; i < tot; i++, uv++) { minmax_v2v2_v2(min_uv, max_uv, (*uv)); - uv++; } bounds_px->xmin = int(ibuf_x * min_uv[0]); @@ -1082,6 +1082,15 @@ static bool pixel_bounds_array( bounds_px->xmax = int(ibuf_x * max_uv[0]) + 1; bounds_px->ymax = int(ibuf_y * max_uv[1]) + 1; + const int d = -1000; + if (bounds_px->xmin < d || bounds_px->ymin < d || bounds_px->xmax < d || bounds_px->ymax < d) { + printf("error! xmin %d xmax %d ymin %d ymax %d\n", + bounds_px->xmin, + bounds_px->xmax, + bounds_px->ymin, + bounds_px->ymax); + } + // printf("%d %d %d %d\n", min_px[0], min_px[1], max_px[0], max_px[1]); /* face uses no UV area when quantized to pixels? */ diff --git a/source/blender/editors/sculpt_paint/paint_intern.hh b/source/blender/editors/sculpt_paint/paint_intern.hh index 04866b09974..10a55c7cc24 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.hh +++ b/source/blender/editors/sculpt_paint/paint_intern.hh @@ -50,6 +50,10 @@ struct CoNo { float no[3]; }; +#include "DNA_listBase.h" +#include "DNA_scene_types.h" +#include "ED_view3d.hh" + /* paint_stroke.cc */ namespace blender::ed::sculpt_paint { @@ -115,7 +119,7 @@ bool paint_brush_tool_poll(bContext *C); */ void paint_cursor_delete_textures(); -/* `paint_vertex.cc` */ +/* paint_vertex.c */ bool weight_paint_poll(bContext *C); bool weight_paint_poll_ignore_tool(bContext *C); @@ -145,12 +149,14 @@ void PAINT_OT_weight_gradient(wmOperatorType *ot); void PAINT_OT_vertex_paint_toggle(wmOperatorType *ot); void PAINT_OT_vertex_paint(wmOperatorType *ot); +unsigned int vpaint_get_current_color(Scene *scene, VPaint *vp, bool secondary); + /** * \note weight-paint has an equivalent function: #ED_wpaint_blend_tool */ unsigned int ED_vpaint_blend_tool(int tool, uint col, uint paintcol, int alpha_i); -/* `paint_vertex_weight_utils.cc` */ +/* paint_vertex_weight_utils.c */ /** * \param weight: Typically the current weight: #MDeformWeight.weight @@ -179,7 +185,7 @@ bool ED_wpaint_ensure_data(bContext *C, /** Return -1 when invalid. */ int ED_wpaint_mirror_vgroup_ensure(Object *ob, int vgroup_active); -/* `paint_vertex_color_ops.cc` */ +/* paint_vertex_color_ops.c */ void PAINT_OT_vertex_color_set(wmOperatorType *ot); void PAINT_OT_vertex_color_from_weight(wmOperatorType *ot); @@ -189,13 +195,13 @@ void PAINT_OT_vertex_color_hsv(wmOperatorType *ot); void PAINT_OT_vertex_color_invert(wmOperatorType *ot); void PAINT_OT_vertex_color_levels(wmOperatorType *ot); -/* `paint_vertex_weight_ops.cc` */ +/* paint_vertex_weight_ops.c */ void PAINT_OT_weight_from_bones(wmOperatorType *ot); void PAINT_OT_weight_sample(wmOperatorType *ot); void PAINT_OT_weight_sample_group(wmOperatorType *ot); -/* `paint_vertex_proj.cc` */ +/* paint_vertex_proj.c */ VertProjHandle *ED_vpaint_proj_handle_create(Depsgraph *depsgraph, Scene *scene, @@ -208,7 +214,7 @@ void ED_vpaint_proj_handle_update(Depsgraph *depsgraph, const float mval_fl[2]); void ED_vpaint_proj_handle_free(VertProjHandle *vp_handle); -/* `paint_image.cc` */ +/* paint_image.c */ struct ImagePaintPartialRedraw { rcti dirty_region; @@ -318,11 +324,11 @@ void paint_curve_mask_cache_update(CurveMaskCache *curve_mask_cache, float radius, const float cursor_position[2]); -/* `sculpt_uv.cc` */ +/* sculpt_uv.c */ void SCULPT_OT_uv_sculpt_stroke(wmOperatorType *ot); -/* paint_utils.cc */ +/* paint_utils.c */ /** * Convert the object-space axis-aligned bounding box (expressed as @@ -365,7 +371,7 @@ bool paint_get_tex_pixel(const MTex *mtex, void paint_sample_color( bContext *C, ARegion *region, int x, int y, bool texpaint_proj, bool palette); -void paint_stroke_operator_properties(wmOperatorType *ot); +void paint_stroke_operator_properties(wmOperatorType *ot, bool mode_skip_save); void BRUSH_OT_curve_preset(wmOperatorType *ot); void BRUSH_OT_sculpt_curves_falloff_preset(wmOperatorType *ot); @@ -393,6 +399,7 @@ bool mask_paint_poll(bContext *C); bool paint_curve_poll(bContext *C); bool facemask_paint_poll(bContext *C); + /** * Uses symm to selectively flip any axis of a coordinate. */ @@ -475,7 +482,7 @@ void PAINT_OT_hide_show_lasso_gesture(wmOperatorType *ot); void PAINT_OT_visibility_invert(wmOperatorType *ot); } // namespace blender::ed::sculpt_paint::hide -/* `paint_mask.cc` */ +/* paint_mask.c */ namespace blender::ed::sculpt_paint::mask { @@ -487,7 +494,7 @@ void PAINT_OT_mask_box_gesture(wmOperatorType *ot); void PAINT_OT_mask_line_gesture(wmOperatorType *ot); } // namespace blender::ed::sculpt_paint::mask -/* `paint_curve.cc` */ +/* paint_curve.c */ void PAINTCURVE_OT_new(wmOperatorType *ot); void PAINTCURVE_OT_add_point(wmOperatorType *ot); diff --git a/source/blender/editors/sculpt_paint/paint_stroke.cc b/source/blender/editors/sculpt_paint/paint_stroke.cc index 6bde9707c8b..a94d968ce23 100644 --- a/source/blender/editors/sculpt_paint/paint_stroke.cc +++ b/source/blender/editors/sculpt_paint/paint_stroke.cc @@ -263,7 +263,6 @@ static bool paint_tool_require_inbetween_mouse_events(Brush *brush, PaintMode mo SCULPT_TOOL_GRAB, SCULPT_TOOL_ROTATE, SCULPT_TOOL_THUMB, - SCULPT_TOOL_SNAKE_HOOK, SCULPT_TOOL_ELASTIC_DEFORM, SCULPT_TOOL_CLOTH, SCULPT_TOOL_BOUNDARY, @@ -427,7 +426,8 @@ static bool paint_brush_update(bContext *C, } /* curve strokes do their own rake calculation */ else if (!(brush->flag & BRUSH_CURVE)) { - if (!paint_calculate_rake_rotation(ups, brush, mouse_init, mode, stroke->rake_started)) { + if (!paint_calculate_rake_rotation( + ups, brush, mouse, mouse_init, mode, stroke->rake_started)) { /* Not enough motion to define an angle. */ if (!stroke->rake_started) { is_dry_run = true; @@ -1073,7 +1073,7 @@ bool paint_supports_dynamic_size(Brush *br, PaintMode mode) switch (mode) { case PaintMode::Sculpt: - if (sculpt_is_grab_tool(br)) { + if (sculpt_is_grab_tool(br) && br->sculpt_tool != SCULPT_TOOL_SNAKE_HOOK) { return false; } break; @@ -1567,7 +1567,8 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event, PaintS { copy_v2_v2(stroke->ups->last_rake, stroke->last_mouse_position); } - paint_calculate_rake_rotation(stroke->ups, br, mouse, mode, true); + paint_calculate_rake_rotation( + stroke->ups, br, mouse, stroke->last_mouse_position, mode, true); } } else if (first_modal || diff --git a/source/blender/editors/sculpt_paint/paint_utils.cc b/source/blender/editors/sculpt_paint/paint_utils.cc index dfaac319397..be3006bc947 100644 --- a/source/blender/editors/sculpt_paint/paint_utils.cc +++ b/source/blender/editors/sculpt_paint/paint_utils.cc @@ -9,6 +9,8 @@ #include #include +#include "MEM_guardedalloc.h" + #include "DNA_material_types.h" #include "DNA_mesh_types.h" #include "DNA_object_types.h" @@ -42,6 +44,7 @@ #include "RNA_access.hh" #include "RNA_define.hh" +#include "RNA_path.hh" #include "RNA_prototypes.h" #include "GPU_matrix.h" @@ -169,7 +172,7 @@ bool paint_get_tex_pixel(const MTex *mtex, return has_rgb; } -void paint_stroke_operator_properties(wmOperatorType *ot) +void paint_stroke_operator_properties(wmOperatorType *ot, bool mode_skip_save) { static const EnumPropertyItem stroke_mode_items[] = { {BRUSH_STROKE_NORMAL, "NORMAL", 0, "Regular", "Apply brush normally"}, @@ -197,7 +200,11 @@ void paint_stroke_operator_properties(wmOperatorType *ot) BRUSH_STROKE_NORMAL, "Stroke Mode", "Action taken when a paint stroke is made"); - RNA_def_property_flag(prop, PropertyFlag(PROP_SKIP_SAVE)); + if (mode_skip_save) { + /* probably a good idea to enable this for all paint modes, since otherwise + keymaps can do weird things if a user forgets to explicitly set this prop - joeedh */ + RNA_def_property_flag(prop, PropertyFlag(PROP_SKIP_SAVE)); + } } /* 3D Paint */ diff --git a/source/blender/editors/sculpt_paint/paint_vertex.cc b/source/blender/editors/sculpt_paint/paint_vertex.cc index d274ed46922..f62417797d1 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex.cc +++ b/source/blender/editors/sculpt_paint/paint_vertex.cc @@ -478,7 +478,7 @@ void update_cache_invariants( } copy_v2_v2(cache->mouse, cache->initial_mouse); - const Brush *brush = vp->paint.brush; + Brush *brush = vp->paint.brush; /* Truly temporary data that isn't stored in properties */ cache->vc = vc; cache->brush = brush; @@ -2059,7 +2059,7 @@ void PAINT_OT_vertex_paint(wmOperatorType *ot) /* flags */ ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING; - paint_stroke_operator_properties(ot); + paint_stroke_operator_properties(ot, false); } /** \} */ @@ -2209,7 +2209,34 @@ bool BKE_object_attributes_active_color_fill(Object *ob, const float fill_color[4], bool only_selected) { - return paint_object_attributes_active_color_fill_ex(ob, ColorPaint4f(fill_color), only_selected); + if (ob->sculpt && ob->mode == OB_MODE_SCULPT && ob->sculpt->pbvh && + ELEM(BKE_pbvh_type(ob->sculpt->pbvh), PBVH_FACES, PBVH_BMESH)) + { + SculptSession *ss = ob->sculpt; + + /* Ensure active attribute is set inside ss->bm properly if + * in PBVH_BMESH mode. + */ + BKE_sculptsession_sync_attributes(ob, static_cast(ob->data), false); + BKE_pbvh_update_active_vcol(ss->pbvh, static_cast(ob->data)); + + Vector nodes = blender::bke::pbvh::search_gather(ss->pbvh, {}); + for (PBVHNode *node : nodes) { + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { + BKE_pbvh_vertex_color_set(ss->pbvh, vd.vertex, fill_color); + } + BKE_pbvh_vertex_iter_end; + + BKE_pbvh_node_mark_update_color(node); + } + + return true; + } + else { + return paint_object_attributes_active_color_fill_ex( + ob, ColorPaint4f(fill_color), only_selected); + } } static int vertex_color_set_exec(bContext *C, wmOperator *op) diff --git a/source/blender/editors/sculpt_paint/paint_weight.cc b/source/blender/editors/sculpt_paint/paint_weight.cc index e37992fedda..7e963461684 100644 --- a/source/blender/editors/sculpt_paint/paint_weight.cc +++ b/source/blender/editors/sculpt_paint/paint_weight.cc @@ -2039,7 +2039,7 @@ void PAINT_OT_weight_paint(wmOperatorType *ot) /* flags */ ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING; - paint_stroke_operator_properties(ot); + paint_stroke_operator_properties(ot, true); } /** \} */ diff --git a/source/blender/editors/sculpt_paint/sculpt.cc b/source/blender/editors/sculpt_paint/sculpt.cc index a617aafe7c6..9170a81f524 100644 --- a/source/blender/editors/sculpt_paint/sculpt.cc +++ b/source/blender/editors/sculpt_paint/sculpt.cc @@ -14,6 +14,7 @@ #include "MEM_guardedalloc.h" +#include "BLI_alloca.h" #include "CLG_log.h" #include "BLI_array_utils.hh" @@ -21,8 +22,11 @@ #include "BLI_blenlib.h" #include "BLI_dial_2d.h" #include "BLI_ghash.h" +#include "BLI_gsqueue.h" +#include "BLI_index_range.hh" #include "BLI_math_geom.h" #include "BLI_math_matrix.h" +#include "BLI_math_vector.hh" #include "BLI_set.hh" #include "BLI_span.hh" #include "BLI_task.h" @@ -33,6 +37,8 @@ #include "DNA_brush_types.h" #include "DNA_customdata_types.h" #include "DNA_key_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" #include "DNA_node_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" @@ -43,6 +49,8 @@ #include "BKE_colortools.hh" #include "BKE_context.hh" #include "BKE_customdata.hh" +#include "BKE_dyntopo.hh" +#include "BKE_global.hh" #include "BKE_image.h" #include "BKE_key.hh" #include "BKE_layer.hh" @@ -59,6 +67,8 @@ #include "BKE_pbvh_api.hh" #include "BKE_report.hh" #include "BKE_scene.hh" +#include "BKE_sculpt.h" +#include "BKE_sculpt.hh" #include "BKE_subdiv_ccg.hh" #include "BKE_subsurf.hh" #include "BLI_math_vector.hh" @@ -66,6 +76,7 @@ #include "NOD_texture.h" #include "DEG_depsgraph.hh" +#include "DEG_depsgraph_query.hh" #include "WM_api.hh" #include "WM_types.hh" @@ -83,13 +94,38 @@ #include "RNA_define.hh" #include "bmesh.hh" +#include "bmesh_idmap.hh" using blender::float3; +using blender::IndexRange; +using blender::int2; using blender::MutableSpan; using blender::Set; using blender::Span; using blender::Vector; +using namespace blender::bke::paint; +using namespace blender::ed::sculpt_paint; + +namespace blender::bke::pbvh { +void split_bmesh_nodes(PBVH *pbvh); +} + +namespace blender::ed::sculpt_paint { +static Vector sculpt_pbvh_gather_generic(Object *ob, + const Brush *brush, + const bool use_original, + const float radius_scale); +} + +static bool is_realtime_restored(Brush *brush) +{ + return ((brush->flag & BRUSH_ANCHORED) || + (ELEM(brush->sculpt_tool, SCULPT_TOOL_GRAB, SCULPT_TOOL_ELASTIC_DEFORM) && + BKE_brush_use_size_pressure(brush)) || + (brush->flag & BRUSH_DRAG_DOT)); +} + static CLG_LogRef LOG = {"ed.sculpt_paint"}; namespace blender::ed::sculpt_paint { @@ -155,12 +191,73 @@ SculptMaskWriteInfo SCULPT_mask_get_for_write(SculptSession *ss) void SCULPT_vertex_random_access_ensure(SculptSession *ss) { - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - BM_mesh_elem_index_ensure(ss->bm, BM_VERT); - BM_mesh_elem_table_ensure(ss->bm, BM_VERT); + if (ss->bm) { + ss->totfaces = ss->faces_num = ss->bm->totface; + ss->totvert = ss->bm->totvert; + + BM_mesh_elem_index_ensure(ss->bm, BM_VERT | BM_EDGE | BM_FACE); + BM_mesh_elem_table_ensure(ss->bm, BM_VERT | BM_EDGE | BM_FACE); } } +namespace blender::ed::sculpt_paint { + +void face_normal_get(const Object *ob, PBVHFaceRef face, float no[3]) +{ + SculptSession *ss = ob->sculpt; + + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_BMESH: { + BMFace *f = (BMFace *)face.i; + + copy_v3_v3(no, f->no); + break; + } + + case PBVH_FACES: + case PBVH_GRIDS: { + Mesh *mesh = BKE_object_get_original_mesh(ob); + no = ::blender::bke::mesh::face_normal_calc( + BKE_pbvh_get_vert_positions(ss->pbvh), + mesh->corner_verts().slice(mesh->faces()[face.i])); + break; + } + default: /* Failed. */ + zero_v3(no); + break; + } +} +} // namespace blender::ed::sculpt_paint + +/* Sculpt PBVH abstraction API + * + * This is read-only, for writing use PBVH vertex iterators. There vd.index matches + * the indices used here. + * + * For multi-resolution, the same vertex in multiple grids is counted multiple times, with + * different index for each grid. */ + +void SCULPT_face_random_access_ensure(SculptSession *ss) +{ + if (ss->bm) { + ss->totfaces = ss->faces_num = ss->bm->totface; + ss->totvert = ss->bm->totvert; + + BM_mesh_elem_index_ensure(ss->bm, BM_FACE); + BM_mesh_elem_table_ensure(ss->bm, BM_FACE); + } +} + +const float *SCULPT_vertex_origco_get(const SculptSession *ss, PBVHVertRef vertex) +{ + return vertex_attr_ptr(vertex, ss->attrs.orig_co); +} + +void SCULPT_vertex_origno_get(const SculptSession *ss, PBVHVertRef vertex, float r_no[3]) +{ + copy_v3_v3(r_no, vertex_attr_ptr(vertex, ss->attrs.orig_no)); +} + int SCULPT_vertex_count_get(const SculptSession *ss) { switch (BKE_pbvh_type(ss->pbvh)) { @@ -198,6 +295,34 @@ const float *SCULPT_vertex_co_get(const SculptSession *ss, PBVHVertRef vertex) return nullptr; } +void SCULPT_vertex_co_set(SculptSession *ss, PBVHVertRef vertex, const float *co) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + if (ss->shapekey_active || ss->deform_modifiers_active) { + MutableSpan positions = BKE_pbvh_get_vert_positions(ss->pbvh); + copy_v3_v3(positions[vertex.i], co); + } + + copy_v3_v3(ss->vert_positions[vertex.i], co); + break; + } + case PBVH_BMESH: + copy_v3_v3(((BMVert *)vertex.i)->co, co); + break; + case PBVH_GRIDS: { + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; + CCGElem *elem = BKE_pbvh_get_grids(ss->pbvh)[grid_index]; + float *vertex_co = CCG_elem_co(key, CCG_elem_offset(key, elem, vertex_index)); + + copy_v3_v3(vertex_co, co); + break; + } + } +} + bool SCULPT_has_loop_colors(const Object *ob) { using namespace blender; @@ -218,7 +343,7 @@ bool SCULPT_has_loop_colors(const Object *ob) bool SCULPT_has_colors(const SculptSession *ss) { - return ss->vcol || ss->mcol; + return ss->bm ? ss->cd_vcol_offset >= 0 : (ss->vcol || ss->mcol); } void SCULPT_vertex_color_get(const SculptSession *ss, PBVHVertRef vertex, float r_color[4]) @@ -235,7 +360,7 @@ void SCULPT_vertex_normal_get(const SculptSession *ss, PBVHVertRef vertex, float { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: { - const Span vert_normals = BKE_pbvh_get_vert_normals(ss->pbvh); + auto vert_normals = BKE_pbvh_get_vert_normals(ss->pbvh); copy_v3_v3(no, vert_normals[vertex.i]); break; } @@ -255,10 +380,15 @@ void SCULPT_vertex_normal_get(const SculptSession *ss, PBVHVertRef vertex, float } } +bool SCULPT_has_persistent_base(SculptSession *ss) +{ + return BKE_sculpt_has_persistent_base(ss); +} + const float *SCULPT_vertex_persistent_co_get(SculptSession *ss, PBVHVertRef vertex) { if (ss->attrs.persistent_co) { - return (const float *)SCULPT_vertex_attr_get(vertex, ss->attrs.persistent_co); + return vertex_attr_ptr(vertex, ss->attrs.persistent_co); } return SCULPT_vertex_co_get(ss, vertex); @@ -286,7 +416,13 @@ void SCULPT_vertex_limit_surface_get(SculptSession *ss, PBVHVertRef vertex, floa switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: case PBVH_BMESH: - copy_v3_v3(r_co, SCULPT_vertex_co_get(ss, vertex)); + if (ss->attrs.limit_surface) { + float *f = vertex_attr_ptr(vertex, ss->attrs.limit_surface); + copy_v3_v3(r_co, f); + } + else { + copy_v3_v3(r_co, SCULPT_vertex_co_get(ss, vertex)); + } break; case PBVH_GRIDS: { const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); @@ -306,7 +442,7 @@ void SCULPT_vertex_limit_surface_get(SculptSession *ss, PBVHVertRef vertex, floa void SCULPT_vertex_persistent_normal_get(SculptSession *ss, PBVHVertRef vertex, float no[3]) { if (ss->attrs.persistent_no) { - copy_v3_v3(no, (float *)SCULPT_vertex_attr_get(vertex, ss->attrs.persistent_no)); + copy_v3_v3(no, vertex_attr_ptr(vertex, ss->attrs.persistent_no)); return; } SCULPT_vertex_normal_get(ss, vertex, no); @@ -325,6 +461,41 @@ float SCULPT_mask_get_at_grids_vert_index(const SubdivCCG &subdiv_ccg, return *CCG_elem_offset_mask(&key, elem, index_in_grid); } +float SCULPT_vertex_mask_get(SculptSession *ss, PBVHVertRef vertex) +{ + using namespace blender; + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + const Mesh *mesh = BKE_pbvh_get_mesh(ss->pbvh); + const bke::AttributeAccessor attributes = mesh->attributes(); + const VArray mask = *attributes.lookup_or_default( + ".sculpt_mask", bke::AttrDomain::Point, 0.0f); + return mask[vertex.i]; + } + case PBVH_BMESH: { + BMVert *v; + int cd_mask = CustomData_get_offset_named(&ss->bm->vdata, CD_PROP_FLOAT, ".sculpt_mask"); + + v = (BMVert *)vertex.i; + return cd_mask != -1 ? BM_ELEM_CD_GET_FLOAT(v, cd_mask) : 0.0f; + } + case PBVH_GRIDS: { + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + + if (key->mask_offset == -1) { + return 0.0f; + } + + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; + CCGElem *elem = ss->subdiv_ccg->grids[grid_index]; + return *CCG_elem_mask(key, CCG_elem_offset(key, elem, vertex_index)); + } + } + + return 0.0f; +} + PBVHVertRef SCULPT_active_vertex_get(SculptSession *ss) { if (ELEM(BKE_pbvh_type(ss->pbvh), PBVH_FACES, PBVH_BMESH, PBVH_GRIDS)) { @@ -379,12 +550,16 @@ namespace blender::ed::sculpt_paint::face_set { int active_face_set_get(SculptSession *ss) { + if (ss->active_face.i == PBVH_REF_NONE) { + return SCULPT_FACE_SET_NONE; + } + switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: if (!ss->face_sets) { return SCULPT_FACE_SET_NONE; } - return ss->face_sets[ss->active_face_index]; + return ss->face_sets[ss->active_face.i]; case PBVH_GRIDS: { if (!ss->face_sets) { return SCULPT_FACE_SET_NONE; @@ -394,11 +569,113 @@ int active_face_set_get(SculptSession *ss) return ss->face_sets[face_index]; } case PBVH_BMESH: + if (ss->cd_faceset_offset != -1 && (ss->active_face.i != PBVH_REF_NONE)) { + BMFace *f = (BMFace *)ss->active_face.i; + return BM_ELEM_CD_GET_INT(f, ss->cd_faceset_offset); + } + return SCULPT_FACE_SET_NONE; } return SCULPT_FACE_SET_NONE; } +void visibility_all_invert(SculptSession *ss) +{ + SCULPT_topology_islands_invalidate(ss); + + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + case PBVH_GRIDS: + blender::array_utils::invert_booleans({ss->hide_poly, ss->totfaces}); + break; + case PBVH_BMESH: { + BMIter iter; + BMFace *f; + + BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { + bool state = BM_elem_flag_test(f, BM_ELEM_HIDDEN); + BM_elem_flag_set(f, BM_ELEM_HIDDEN, state ^ true); + } + break; + } + } +} + +void visibility_set(SculptSession *ss, int face_set, bool visible) +{ + BLI_assert(ss->face_sets != nullptr); + BLI_assert(ss->hide_poly != nullptr); + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + case PBVH_GRIDS: + for (int i = 0; i < ss->totfaces; i++) { + if (ss->face_sets[i] != face_set) { + continue; + } + ss->hide_poly[i] = !visible; + } + break; + case PBVH_BMESH: { + BMIter iter; + BMFace *f; + + BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { + int fset = BM_ELEM_CD_GET_INT(f, ss->cd_faceset_offset); + int node = BM_ELEM_CD_GET_INT(f, ss->cd_face_node_offset); + + if (fset != face_set) { + continue; + } + + BM_elem_flag_set(f, BM_ELEM_HIDDEN, !visible); + + if (node != DYNTOPO_NODE_NONE) { + BKE_pbvh_vert_tag_update_normal_triangulation(BKE_pbvh_node_from_index(ss->pbvh, node)); + } + } + break; + } + } +} + +void visibility_all_set(Object *ob, bool visible) +{ + SculptSession *ss = ob->sculpt; + + if (!ss->bm && visible && !ss->attrs.hide_poly) { + /* Nothing is hidden. */ + return; + } + + SCULPT_topology_islands_invalidate(ss); + SCULPT_face_random_access_ensure(ss); + + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + case PBVH_GRIDS: + /* Note: no need to use the generic loop in PBVH_BMESH, just memset ss->hide_poly. */ + blender::MutableSpan(ss->hide_poly, ss->totfaces).fill(!visible); + break; + case PBVH_BMESH: { + BLI_assert(ss->hide_poly != nullptr); + + if (visible) { + if (ss->attrs.hide_poly) { + BKE_sculpt_attribute_destroy(ob, ss->attrs.hide_poly); + } + ss->hide_poly = nullptr; + } + else { + if (!ss->hide_poly) { + ss->hide_poly = BKE_sculpt_hide_poly_ensure(ob); + } + memset(ss->hide_poly, !visible, sizeof(bool) * ss->totfaces); + } + break; + } + } +} + } // namespace blender::ed::sculpt_paint::face_set namespace blender::ed::sculpt_paint::hide { @@ -414,7 +691,7 @@ bool vert_visible_get(const SculptSession *ss, PBVHVertRef vertex) return !hide_vert[vertex.i]; } case PBVH_BMESH: - return !BM_elem_flag_test((BMVert *)vertex.i, BM_ELEM_HIDDEN); + return !BM_elem_flag_test(((BMVert *)vertex.i), BM_ELEM_HIDDEN); case PBVH_GRIDS: { const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); const int grid_index = vertex.i / key->grid_area; @@ -441,14 +718,62 @@ bool vert_any_face_visible_get(SculptSession *ss, PBVHVertRef vertex) } return false; } - case PBVH_BMESH: - return true; + case PBVH_BMESH: { + BMIter iter; + BMLoop *l; + BMVert *v = (BMVert *)vertex.i; + + BM_ITER_ELEM (l, &iter, v, BM_LOOPS_OF_VERT) { + if (!BM_elem_flag_test(l->f, BM_ELEM_HIDDEN)) { + return true; + } + } + + return false; + } case PBVH_GRIDS: return true; } return true; } +void face_set(SculptSession *ss, int face_set, bool visible) +{ + BLI_assert(ss->face_sets != nullptr); + BLI_assert(ss->hide_poly != nullptr); + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + case PBVH_GRIDS: + for (int i = 0; i < ss->totfaces; i++) { + if (ss->face_sets[i] != face_set) { + continue; + } + ss->hide_poly[i] = !visible; + } + break; + case PBVH_BMESH: { + BMIter iter; + BMFace *f; + + BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { + int fset = BM_ELEM_CD_GET_INT(f, ss->cd_faceset_offset); + int node = BM_ELEM_CD_GET_INT(f, ss->cd_face_node_offset); + + if (fset != face_set) { + continue; + } + + BM_elem_flag_set(f, BM_ELEM_HIDDEN, !visible); + + if (node != DYNTOPO_NODE_NONE) { + BKE_pbvh_vert_tag_update_normal_triangulation(BKE_pbvh_node_from_index(ss->pbvh, node)); + } + } + break; + } + } +} + bool vert_all_faces_visible_get(const SculptSession *ss, PBVHVertRef vertex) { switch (BKE_pbvh_type(ss->pbvh)) { @@ -499,7 +824,6 @@ bool vert_all_faces_visible_get(const SculptSession *ss, PBVHVertRef vertex) } return true; } - } // namespace blender::ed::sculpt_paint::hide namespace blender::ed::sculpt_paint::face_set { @@ -519,8 +843,23 @@ int vert_face_set_get(SculptSession *ss, PBVHVertRef vertex) } return face_set; } - case PBVH_BMESH: - return 0; + case PBVH_BMESH: { + BMIter iter; + BMLoop *l; + BMVert *v = (BMVert *)vertex.i; + int ret = -1; + + BM_ITER_ELEM (l, &iter, v, BM_LOOPS_OF_VERT) { + int fset = BM_ELEM_CD_GET_INT(l->f, ss->cd_faceset_offset); + fset = abs(fset); + + if (fset > ret) { + ret = fset; + } + } + + return ret; + } case PBVH_GRIDS: { if (!ss->face_sets) { return SCULPT_FACE_SET_NONE; @@ -534,6 +873,60 @@ int vert_face_set_get(SculptSession *ss, PBVHVertRef vertex) return 0; } +void vert_face_set_set(SculptSession *ss, PBVHVertRef vertex, int face_set) +{ + BKE_sculpt_boundary_flag_update(ss, vertex); + + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + BLI_assert(ss->face_sets != nullptr); + for (const int face_index : ss->vert_to_face_map[vertex.i]) { + if (ss->hide_poly && ss->hide_poly[face_index]) { + /* Skip hidden faces connected to the vertex. */ + continue; + } + + for (int vert_i : ss->corner_verts.slice(ss->faces[face_index])) { + BKE_sculpt_boundary_flag_update(ss, BKE_pbvh_make_vref(vert_i)); + } + + ss->face_sets[face_index] = face_set; + } + + break; + } + case PBVH_BMESH: { + BMIter iter; + BMLoop *l; + BMVert *v = (BMVert *)vertex.i; + + BM_ITER_ELEM (l, &iter, v, BM_LOOPS_OF_VERT) { + int fset = BM_ELEM_CD_GET_INT(l->f, ss->cd_faceset_offset); + if (!BM_elem_flag_test(l->f, BM_ELEM_HIDDEN) && fset != face_set) { + BM_ELEM_CD_SET_INT(l->f, ss->cd_faceset_offset, abs(face_set)); + } + + PBVHVertRef vertex2 = {(intptr_t)l->v}; + BKE_sculpt_boundary_flag_update(ss, vertex2); + } + + break; + } + case PBVH_GRIDS: { + BLI_assert(ss->face_sets != nullptr); + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int face_index = BKE_subdiv_ccg_grid_to_face_index(*ss->subdiv_ccg, grid_index); + if (ss->hide_poly && ss->hide_poly[face_index]) { + /* Skip the vertex if it's in a hidden face. */ + return; + } + ss->face_sets[face_index] = face_set; + break; + } + } +} + bool vert_has_face_set(SculptSession *ss, PBVHVertRef vertex, int face_set) { switch (BKE_pbvh_type(ss->pbvh)) { @@ -548,8 +941,38 @@ bool vert_has_face_set(SculptSession *ss, PBVHVertRef vertex, int face_set) } return false; } - case PBVH_BMESH: - return true; + case PBVH_BMESH: { + BMEdge *e; + BMVert *v = (BMVert *)vertex.i; + + if (ss->cd_faceset_offset == -1) { + return false; + } + + e = v->e; + + if (UNLIKELY(!e)) { + return false; + } + + do { + BMLoop *l = e->l; + + if (UNLIKELY(!l)) { + continue; + } + + do { + BMFace *f = l->f; + + if (abs(BM_ELEM_CD_GET_INT(f, ss->cd_faceset_offset)) == abs(face_set)) { + return true; + } + } while ((l = l->radial_next) != e->l); + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + return false; + } case PBVH_GRIDS: { if (!ss->face_sets) { return face_set == SCULPT_FACE_SET_NONE; @@ -652,14 +1075,13 @@ bool vert_has_unique_face_set(SculptSession *ss, PBVHVertRef vertex) /* Sculpt Neighbor Iterators */ -#define SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY 256 - static void sculpt_vertex_neighbor_add(SculptVertexNeighborIter *iter, PBVHVertRef neighbor, + PBVHEdgeRef edge, int neighbor_index) { for (int i = 0; i < iter->size; i++) { - if (iter->neighbors[i].i == neighbor.i) { + if (iter->neighbors[i].vertex.i == neighbor.i) { return; } } @@ -668,55 +1090,126 @@ static void sculpt_vertex_neighbor_add(SculptVertexNeighborIter *iter, iter->capacity += SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; if (iter->neighbors == iter->neighbors_fixed) { - iter->neighbors = static_cast( - MEM_mallocN(iter->capacity * sizeof(PBVHVertRef), "neighbor array")); - memcpy(iter->neighbors, iter->neighbors_fixed, sizeof(PBVHVertRef) * iter->size); - } - else { - iter->neighbors = static_cast(MEM_reallocN_id( - iter->neighbors, iter->capacity * sizeof(PBVHVertRef), "neighbor array")); - } + iter->neighbors = (struct _SculptNeighborRef *)MEM_mallocN( + iter->capacity * sizeof(*iter->neighbors), "neighbor array"); + iter->neighbor_indices = (int *)MEM_mallocN(iter->capacity * sizeof(*iter->neighbor_indices), + "neighbor array"); - if (iter->neighbor_indices == iter->neighbor_indices_fixed) { - iter->neighbor_indices = static_cast( - MEM_mallocN(iter->capacity * sizeof(int), "neighbor array")); - memcpy(iter->neighbor_indices, iter->neighbor_indices_fixed, sizeof(int) * iter->size); + memcpy((void *)iter->neighbors, + (void *)iter->neighbors_fixed, + sizeof(*iter->neighbors) * iter->size); + memcpy((void *)iter->neighbor_indices, + (void *)iter->neighbor_indices_fixed, + sizeof(*iter->neighbor_indices) * iter->size); } else { - iter->neighbor_indices = static_cast( - MEM_reallocN_id(iter->neighbor_indices, iter->capacity * sizeof(int), "neighbor array")); + iter->neighbors = (struct _SculptNeighborRef *)MEM_reallocN_id( + iter->neighbors, iter->capacity * sizeof(*iter->neighbors), "neighbor array"); + iter->neighbor_indices = (int *)MEM_reallocN_id(iter->neighbor_indices, + iter->capacity * + sizeof(*iter->neighbor_indices), + "neighbor array"); } } - iter->neighbors[iter->size] = neighbor; + iter->neighbors[iter->size].vertex = neighbor; + iter->neighbors[iter->size].edge = edge; iter->neighbor_indices[iter->size] = neighbor_index; iter->size++; } -static void sculpt_vertex_neighbors_get_bmesh(PBVHVertRef vertex, SculptVertexNeighborIter *iter) +static void sculpt_vertex_neighbor_add_nocheck(SculptVertexNeighborIter *iter, + PBVHVertRef neighbor, + PBVHEdgeRef edge, + int neighbor_index) { - BMVert *v = (BMVert *)vertex.i; - BMIter liter; - BMLoop *l; + if (iter->size >= iter->capacity) { + iter->capacity += SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; + + if (iter->neighbors == iter->neighbors_fixed) { + iter->neighbors = (struct _SculptNeighborRef *)MEM_mallocN( + iter->capacity * sizeof(*iter->neighbors), "neighbor array"); + iter->neighbor_indices = (int *)MEM_mallocN(iter->capacity * sizeof(*iter->neighbor_indices), + "neighbor array"); + + memcpy(iter->neighbors, iter->neighbors_fixed, sizeof(*iter->neighbors) * iter->size); + + memcpy(iter->neighbor_indices, + iter->neighbor_indices_fixed, + sizeof(*iter->neighbor_indices) * iter->size); + } + else { + iter->neighbors = (struct _SculptNeighborRef *)MEM_reallocN_id( + iter->neighbors, iter->capacity * sizeof(*iter->neighbors), "neighbor array"); + iter->neighbor_indices = (int *)MEM_reallocN_id(iter->neighbor_indices, + iter->capacity * + sizeof(*iter->neighbor_indices), + "neighbor array"); + } + } + + iter->neighbors[iter->size].vertex = neighbor; + iter->neighbors[iter->size].edge = edge; + + iter->neighbor_indices[iter->size] = neighbor_index; + iter->size++; +} + +static void sculpt_vertex_neighbors_get_bmesh(const SculptSession *ss, + PBVHVertRef index, + SculptVertexNeighborIter *iter) +{ + BMVert *v = (BMVert *)index.i; + + iter->is_duplicate = false; + iter->size = 0; iter->num_duplicates = 0; + iter->has_edge = true; iter->capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; iter->neighbors = iter->neighbors_fixed; iter->neighbor_indices = iter->neighbor_indices_fixed; + iter->i = 0; + iter->no_free = false; - BM_ITER_ELEM (l, &liter, v, BM_LOOPS_OF_VERT) { - const BMVert *adj_v[2] = {l->prev->v, l->next->v}; - for (int i = 0; i < ARRAY_SIZE(adj_v); i++) { - const BMVert *v_other = adj_v[i]; - if (v_other != v) { - sculpt_vertex_neighbor_add( - iter, BKE_pbvh_make_vref(intptr_t(v_other)), BM_elem_index_get(v_other)); - } + // cache profiling revealed a hotspot here, don't use BM_ITER + BMEdge *e = v->e; + + if (!v->e) { + return; + } + + BMEdge *e2 = nullptr; + + do { + BMVert *v2; + e2 = BM_DISK_EDGE_NEXT(e, v); + v2 = v == e->v1 ? e->v2 : e->v1; + + uint8_t flag = *BM_ELEM_CD_PTR(v2, ss->attrs.flags->bmesh_cd_offset); + + if (!(flag & SCULPTFLAG_VERT_FSET_HIDDEN)) { + sculpt_vertex_neighbor_add_nocheck(iter, + BKE_pbvh_make_vref((intptr_t)v2), + BKE_pbvh_make_eref((intptr_t)e), + BM_elem_index_get(v2)); + } + } while ((e = e2) != v->e); + + if (ss->fake_neighbors.use_fake_neighbors) { + int index = BM_elem_index_get(v); + + BLI_assert(ss->fake_neighbors.fake_neighbor_index != nullptr); + if (ss->fake_neighbors.fake_neighbor_index[index].i != FAKE_NEIGHBOR_NONE) { + sculpt_vertex_neighbor_add(iter, + ss->fake_neighbors.fake_neighbor_index[index], + BKE_pbvh_make_eref(PBVH_REF_NONE), + ss->fake_neighbors.fake_neighbor_index[index].i); } } } -static void sculpt_vertex_neighbors_get_faces(SculptSession *ss, +static void sculpt_vertex_neighbors_get_faces(const SculptSession *ss, PBVHVertRef vertex, SculptVertexNeighborIter *iter) { @@ -725,71 +1218,125 @@ static void sculpt_vertex_neighbors_get_faces(SculptSession *ss, iter->capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; iter->neighbors = iter->neighbors_fixed; iter->neighbor_indices = iter->neighbor_indices_fixed; + iter->is_duplicate = false; + iter->has_edge = true; + iter->no_free = false; - for (const int face_i : ss->vert_to_face_map[vertex.i]) { - if (ss->hide_poly && ss->hide_poly[face_i]) { - /* Skip connectivity from hidden faces. */ - continue; - } - const blender::IndexRange face = ss->faces[face_i]; - const blender::int2 f_adj_v = blender::bke::mesh::face_find_adjacent_verts( - face, ss->corner_verts, vertex.i); - for (int j = 0; j < 2; j++) { - if (f_adj_v[j] != vertex.i) { - sculpt_vertex_neighbor_add(iter, BKE_pbvh_make_vref(f_adj_v[j]), f_adj_v[j]); - } - } + int *edges = (int *)BLI_array_alloca(edges, SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY); + int *unused_polys = (int *)BLI_array_alloca(unused_polys, + SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY * 2); + bool heap_alloc = false; + int len = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; + + BKE_pbvh_pmap_to_edges(ss->pbvh, vertex, &edges, &len, &heap_alloc, &unused_polys); + + /* Length of array is now in len. */ + for (int i = 0; i < len; i++) { + const int2 &e = ss->edges[edges[i]]; + int v2 = e[0] == vertex.i ? e[1] : e[0]; + + sculpt_vertex_neighbor_add(iter, BKE_pbvh_make_vref(v2), BKE_pbvh_make_eref(edges[i]), v2); + } + + if (heap_alloc) { + MEM_freeN(unused_polys); + MEM_freeN(edges); } if (ss->fake_neighbors.use_fake_neighbors) { BLI_assert(ss->fake_neighbors.fake_neighbor_index != nullptr); - if (ss->fake_neighbors.fake_neighbor_index[vertex.i] != FAKE_NEIGHBOR_NONE) { - sculpt_vertex_neighbor_add( - iter, - BKE_pbvh_make_vref(ss->fake_neighbors.fake_neighbor_index[vertex.i]), - ss->fake_neighbors.fake_neighbor_index[vertex.i]); + if (ss->fake_neighbors.fake_neighbor_index[vertex.i].i != FAKE_NEIGHBOR_NONE) { + sculpt_vertex_neighbor_add(iter, + ss->fake_neighbors.fake_neighbor_index[vertex.i], + BKE_pbvh_make_eref(PBVH_REF_NONE), + vertex.i); } } } -static void sculpt_vertex_neighbors_get_grids(SculptSession *ss, +static void sculpt_vertex_neighbors_get_faces_vemap(const SculptSession *ss, + PBVHVertRef vertex, + SculptVertexNeighborIter *iter) +{ + iter->size = 0; + iter->num_duplicates = 0; + iter->capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; + iter->neighbors = iter->neighbors_fixed; + iter->neighbor_indices = iter->neighbor_indices_fixed; + iter->is_duplicate = false; + iter->no_free = false; + + for (int edge : ss->vert_to_edge_map[vertex.i]) { + const int2 &e = ss->edges[edge]; + + unsigned int v = e[0] == (unsigned int)vertex.i ? e[1] : e[0]; + int8_t flag = vertex_attr_get(vertex, ss->attrs.flags); + + if (flag & SCULPTFLAG_VERT_FSET_HIDDEN) { + /* Skip connectivity from hidden faces. */ + continue; + } + + sculpt_vertex_neighbor_add_nocheck(iter, BKE_pbvh_make_vref(v), BKE_pbvh_make_eref(edge), v); + } + + if (ss->fake_neighbors.use_fake_neighbors) { + BLI_assert(ss->fake_neighbors.fake_neighbor_index != nullptr); + if (ss->fake_neighbors.fake_neighbor_index[vertex.i].i != FAKE_NEIGHBOR_NONE) { + sculpt_vertex_neighbor_add(iter, + ss->fake_neighbors.fake_neighbor_index[vertex.i], + BKE_pbvh_make_eref(PBVH_REF_NONE), + ss->fake_neighbors.fake_neighbor_index[vertex.i].i); + } + } +} + +static void sculpt_vertex_neighbors_get_grids(const SculptSession *ss, const PBVHVertRef vertex, const bool include_duplicates, SculptVertexNeighborIter *iter) { + int index = (int)vertex.i; + /* TODO: optimize this. We could fill #SculptVertexNeighborIter directly, * maybe provide coordinate and mask pointers directly rather than converting * back and forth between #CCGElem and global index. */ const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int index_in_grid = vertex.i - grid_index * key->grid_area; + const int grid_index = index / key->grid_area; + const int vertex_index = index - grid_index * key->grid_area; SubdivCCGCoord coord{}; coord.grid_index = grid_index; - coord.x = index_in_grid % key->grid_size; - coord.y = index_in_grid / key->grid_size; + coord.x = vertex_index % key->grid_size; + coord.y = vertex_index / key->grid_size; SubdivCCGNeighbors neighbors; BKE_subdiv_ccg_neighbor_coords_get(*ss->subdiv_ccg, coord, include_duplicates, neighbors); + iter->is_duplicate = include_duplicates; + iter->size = 0; iter->num_duplicates = neighbors.num_duplicates; iter->capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; iter->neighbors = iter->neighbors_fixed; iter->neighbor_indices = iter->neighbor_indices_fixed; + iter->no_free = false; for (int i = 0; i < neighbors.size; i++) { - int v = neighbors.coords[i].grid_index * key->grid_area + - neighbors.coords[i].y * key->grid_size + neighbors.coords[i].x; + int idx = neighbors.coords[i].grid_index * key->grid_area + + neighbors.coords[i].y * key->grid_size + neighbors.coords[i].x; - sculpt_vertex_neighbor_add(iter, BKE_pbvh_make_vref(v), v); + sculpt_vertex_neighbor_add( + iter, BKE_pbvh_make_vref(idx), BKE_pbvh_make_eref(PBVH_REF_NONE), idx); } if (ss->fake_neighbors.use_fake_neighbors) { BLI_assert(ss->fake_neighbors.fake_neighbor_index != nullptr); - if (ss->fake_neighbors.fake_neighbor_index[vertex.i] != FAKE_NEIGHBOR_NONE) { - int v = ss->fake_neighbors.fake_neighbor_index[vertex.i]; - sculpt_vertex_neighbor_add(iter, BKE_pbvh_make_vref(v), v); + if (ss->fake_neighbors.fake_neighbor_index[index].i != FAKE_NEIGHBOR_NONE) { + sculpt_vertex_neighbor_add(iter, + ss->fake_neighbors.fake_neighbor_index[index], + BKE_pbvh_make_eref(PBVH_REF_NONE), + ss->fake_neighbors.fake_neighbor_index[index].i); } } @@ -798,17 +1345,26 @@ static void sculpt_vertex_neighbors_get_grids(SculptSession *ss, } } -void SCULPT_vertex_neighbors_get(SculptSession *ss, +void SCULPT_vertex_neighbors_get(const SculptSession *ss, const PBVHVertRef vertex, const bool include_duplicates, SculptVertexNeighborIter *iter) { + iter->no_free = false; + switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: - sculpt_vertex_neighbors_get_faces(ss, vertex, iter); + /* use vemap if it exists, so result is in disk cycle order */ + if (!ss->vert_to_edge_map.is_empty()) { + blender::bke::pbvh::set_vemap(ss->pbvh, ss->vert_to_edge_map); + sculpt_vertex_neighbors_get_faces_vemap(ss, vertex, iter); + } + else { + sculpt_vertex_neighbors_get_faces(ss, vertex, iter); + } return; case PBVH_BMESH: - sculpt_vertex_neighbors_get_bmesh(vertex, iter); + sculpt_vertex_neighbors_get_bmesh(ss, vertex, iter); return; case PBVH_GRIDS: sculpt_vertex_neighbors_get_grids(ss, vertex, include_duplicates, iter); @@ -821,47 +1377,6 @@ static bool sculpt_check_boundary_vertex_in_base_mesh(const SculptSession *ss, c return ss->vertex_info.boundary[index]; } -bool SCULPT_vertex_is_boundary(const SculptSession *ss, const PBVHVertRef vertex) -{ - using namespace blender::ed::sculpt_paint; - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - if (!hide::vert_all_faces_visible_get(ss, vertex)) { - return true; - } - return sculpt_check_boundary_vertex_in_base_mesh(ss, vertex.i); - } - case PBVH_BMESH: { - BMVert *v = (BMVert *)vertex.i; - return BM_vert_is_boundary(v); - } - - case PBVH_GRIDS: { - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int index_in_grid = vertex.i - grid_index * key->grid_area; - SubdivCCGCoord coord{}; - coord.grid_index = grid_index; - coord.x = index_in_grid % key->grid_size; - coord.y = index_in_grid / key->grid_size; - int v1, v2; - const SubdivCCGAdjacencyType adjacency = BKE_subdiv_ccg_coarse_mesh_adjacency_info_get( - *ss->subdiv_ccg, coord, ss->corner_verts, ss->faces, v1, v2); - switch (adjacency) { - case SUBDIV_CCG_ADJACENT_VERTEX: - return sculpt_check_boundary_vertex_in_base_mesh(ss, v1); - case SUBDIV_CCG_ADJACENT_EDGE: - return sculpt_check_boundary_vertex_in_base_mesh(ss, v1) && - sculpt_check_boundary_vertex_in_base_mesh(ss, v2); - case SUBDIV_CCG_ADJACENT_NONE: - return false; - } - } - } - - return false; -} - /* Utilities */ bool SCULPT_stroke_is_main_symmetry_pass(blender::ed::sculpt_paint::StrokeCache *cache) @@ -1018,10 +1533,11 @@ void add_initial(SculptFloodFill *flood, PBVHVertRef vertex) flood->queue.push(vertex); } -void add_and_skip_initial(SculptFloodFill *flood, PBVHVertRef vertex) +void add_and_skip_initial(SculptSession *ss, SculptFloodFill *flood, PBVHVertRef vertex) { flood->queue.push(vertex); - flood->visited_verts[vertex.i].set(vertex.i); + flood->visited_verts[BKE_pbvh_vertex_to_index(ss->pbvh, vertex)].set( + BKE_pbvh_vertex_to_index(ss->pbvh, vertex)); } void add_initial_with_symmetry( @@ -1135,7 +1651,8 @@ static bool sculpt_tool_needs_original(const char sculpt_tool) SCULPT_TOOL_ELASTIC_DEFORM, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_BOUNDARY, - SCULPT_TOOL_POSE); + SCULPT_TOOL_POSE, + SCULPT_TOOL_PAINT); } static bool sculpt_tool_is_proxy_used(const char sculpt_tool) @@ -1202,7 +1719,44 @@ enum StrokeFlags { CLIP_Z = 4, }; -void SCULPT_orig_vert_data_unode_init(SculptOrigVertData *data, +void SCULPT_orig_vert_data_init(SculptOrigVertData *data, + Object *ob, + PBVHNode * /*node*/, + undo::Type type) +{ + SculptSession *ss = ob->sculpt; + BMesh *bm = ss->bm; + + data->ss = ss; + data->datatype = type; + + if (bm) { + data->bm_log = ss->bm_log; + } +} + +/** + * DEPRECATED use Update a #SculptOrigVertData for a particular vertex from the PBVH iterator. + */ +void SCULPT_orig_vert_data_update(SculptOrigVertData *orig_data, PBVHVertRef vertex) +{ + float *co = nullptr, *no = nullptr, *color = nullptr, *mask = nullptr; + + get_original_vertex(orig_data->ss, vertex, &co, &no, &color, &mask); + + if ((orig_data->datatype & undo::Type::Position) != undo::Type::None) { + orig_data->co = co; + orig_data->no = no; + } + if ((orig_data->datatype & undo::Type::Color) != undo::Type::None) { + orig_data->col = color; + } + if ((orig_data->datatype & undo::Type::Mask) != undo::Type::None) { + orig_data->mask = *mask; + } +} + +void SCULPT_orig_face_data_unode_init(SculptOrigFaceData *data, Object *ob, blender::ed::sculpt_paint::undo::Node *unode) { @@ -1216,45 +1770,23 @@ void SCULPT_orig_vert_data_unode_init(SculptOrigVertData *data, data->bm_log = ss->bm_log; } else { - data->coords = reinterpret_cast(data->unode->position.data()); - data->normals = reinterpret_cast(data->unode->normal.data()); - data->vmasks = data->unode->mask.data(); - data->colors = reinterpret_cast(data->unode->col.data()); + data->face_sets = unode->face_sets.data(); } } -void SCULPT_orig_vert_data_init(SculptOrigVertData *data, +void SCULPT_orig_face_data_init(SculptOrigFaceData *data, Object *ob, PBVHNode *node, blender::ed::sculpt_paint::undo::Type type) { using namespace blender::ed::sculpt_paint; undo::Node *unode = undo::push_node(ob, node, type); - SCULPT_orig_vert_data_unode_init(data, ob, unode); } -void SCULPT_orig_vert_data_update(SculptOrigVertData *orig_data, PBVHVertexIter *iter) +void SCULPT_orig_face_data_update(SculptOrigFaceData *orig_data, PBVHFaceIter *iter) { - using namespace blender::ed::sculpt_paint; - if (orig_data->unode->type == undo::Type::Position) { - if (orig_data->bm_log) { - BM_log_original_vert_data(orig_data->bm_log, iter->bm_vert, &orig_data->co, &orig_data->no); - } - else { - orig_data->co = orig_data->coords[iter->i]; - orig_data->no = orig_data->normals[iter->i]; - } - } - else if (orig_data->unode->type == undo::Type::Color) { - orig_data->col = orig_data->colors[iter->i]; - } - else if (orig_data->unode->type == undo::Type::Mask) { - if (orig_data->bm_log) { - orig_data->mask = BM_log_original_mask(orig_data->bm_log, iter->bm_vert); - } - else { - orig_data->mask = orig_data->vmasks[iter->i]; - } + if (orig_data->unode->type == undo::Type::FaceSet) { + orig_data->face_set = orig_data->face_sets ? orig_data->face_sets[iter->i] : false; } } @@ -1274,17 +1806,10 @@ static void sculpt_rake_data_update(SculptRakeData *srd, const float co[3]) namespace blender::ed::sculpt_paint::dyntopo { -bool stroke_is_dyntopo(const SculptSession *ss, const Brush *brush) +bool stroke_is_dyntopo(const SculptSession *ss, const Sculpt *sd, const Brush *brush) { - return ((BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) && - - (!ss->cache || (!ss->cache->alt_smooth)) && - - /* Requires mesh restore, which doesn't work with - * dynamic-topology. */ - !(brush->flag & BRUSH_ANCHORED) && !(brush->flag & BRUSH_DRAG_DOT) && - - SCULPT_TOOL_HAS_DYNTOPO(brush->sculpt_tool)); + return ((BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) && SCULPT_TOOL_HAS_DYNTOPO(brush->sculpt_tool) && + !(brush->dyntopo.flag & DYNTOPO_DISABLED) && (sd->flags & SCULPT_DYNTOPO_ENABLED)); } } // namespace blender::ed::sculpt_paint::dyntopo @@ -1297,111 +1822,99 @@ bool stroke_is_dyntopo(const SculptSession *ss, const Brush *brush) namespace blender::ed::sculpt_paint { -static void paint_mesh_restore_node(Object *ob, const undo::Type type, PBVHNode *node) +static void paint_mesh_restore_node(Object *ob, + const undo::Type type, + SculptMaskWriteInfo &mask_write, + PBVHNode *node) { + SculptSession *ss = ob->sculpt; - undo::Node *unode; - if (ss->bm) { - unode = undo::push_node(ob, node, type); - } - else { - unode = undo::get_node(node, type); - } - - if (!unode) { - return; - } - switch (type) { - case undo::Type::Mask: { - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - Mesh &mesh = *static_cast(ob->data); - bke::MutableAttributeAccessor attributes = mesh.attributes_for_write(); - bke::SpanAttributeWriter mask = attributes.lookup_or_add_for_write_span( - ".sculpt_mask", bke::AttrDomain::Point); - array_utils::scatter( - unode->mask.as_span(), BKE_pbvh_node_get_unique_vert_indices(node), mask.span); - mask.finish(); - break; - } - case PBVH_BMESH: { - const int offset = CustomData_get_offset_named( - &ss->bm->vdata, CD_PROP_FLOAT, ".sculpt_mask"); - if (offset != -1) { - for (BMVert *vert : BKE_pbvh_bmesh_node_unique_verts(node)) { - const float orig_mask = BM_log_original_mask(ss->bm_log, vert); - BM_ELEM_CD_SET_FLOAT(vert, offset, orig_mask); - } - } - break; - } - case PBVH_GRIDS: { - SubdivCCG &subdiv_ccg = *ss->subdiv_ccg; - const BitGroupVector<> grid_hidden = subdiv_ccg.grid_hidden; - const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg); - const Span grids = subdiv_ccg.grids; - int index = 0; - for (const int grid : unode->grids) { - CCGElem *elem = grids[grid]; - for (const int i : IndexRange(key.grid_area)) { - if (grid_hidden.is_empty() || !grid_hidden[grid][i]) { - *CCG_elem_offset_mask(&key, elem, i) = unode->mask[index]; - } - index++; - } - } - break; - } - } + case undo::Type::Mask: BKE_pbvh_node_mark_update_mask(node); break; - } - case undo::Type::Color: { - SculptOrigVertData orig_vert_data; - SCULPT_orig_vert_data_unode_init(&orig_vert_data, ob, unode); - PBVHVertexIter vd; - BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_vert_data, &vd); - SCULPT_vertex_color_set(ss, vd.vertex, orig_vert_data.col); - } - BKE_pbvh_vertex_iter_end; + case undo::Type::Color: BKE_pbvh_node_mark_update_color(node); break; - } - case undo::Type::FaceSet: { - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - case PBVH_GRIDS: { - const Span face_sets = unode->face_sets; - const Span faces = unode->face_indices; - bke::SpanAttributeWriter attribute = face_set::ensure_face_sets_mesh(*ob); - blender::array_utils::scatter(face_sets, faces, attribute.span); - attribute.finish(); - break; - } - case PBVH_BMESH: - break; - } + case undo::Type::FaceSet: BKE_pbvh_node_mark_update_face_sets(node); break; - } - case undo::Type::Position: { - SculptOrigVertData orig_vert_data; - SCULPT_orig_vert_data_unode_init(&orig_vert_data, ob, unode); - PBVHVertexIter vd; - BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_vert_data, &vd); - copy_v3_v3(vd.co, orig_vert_data.co); - } - BKE_pbvh_vertex_iter_end; + case undo::Type::Position: BKE_pbvh_node_mark_update(node); break; - } default: break; } + + PBVHVertexIter vd; + + bool modified = false; + + if (!ss->bm && type != undo::Type::None && (type & undo::Type::FaceSet) != undo::Type::None) { + undo::Node *unode = undo::get_node(node, undo::Type::FaceSet); + + SculptOrigFaceData orig_face_data; + SCULPT_orig_face_data_unode_init(&orig_face_data, ob, unode); + + PBVHFaceIter fd; + BKE_pbvh_face_iter_begin (ss->pbvh, node, fd) { + SCULPT_orig_face_data_update(&orig_face_data, &fd); + + if (fd.face_set) { + *fd.face_set = orig_face_data.face_set; + } + } + + BKE_pbvh_face_iter_end(fd); + + if ((type & ~undo::Type::FaceSet) != undo::Type::None) { + return; + } + } + + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { + SCULPT_vertex_check_origdata(ss, vd.vertex); + float *origco = vertex_attr_ptr(vd.vertex, ss->attrs.orig_co); + + if ((type & undo::Type::Position) != undo::Type::None) { + if (len_squared_v3v3(vd.co, origco) > FLT_EPSILON) { + modified = true; + } + + copy_v3_v3(vd.co, origco); + } + + if ((type & undo::Type::Mask) != undo::Type::None && ss->attrs.orig_mask) { + float origmask = vertex_attr_get(vd.vertex, ss->attrs.orig_mask); + + if ((vd.mask - origmask) * (vd.mask - origmask) > FLT_EPSILON) { + modified = true; + } + + SCULPT_mask_vert_set(BKE_pbvh_type(ss->pbvh), mask_write, origmask, vd); + } + + if ((type & undo::Type::Color) != undo::Type::None && ss->attrs.orig_color) { + float *origcolor = vertex_attr_ptr(vd.vertex, ss->attrs.orig_color); + float color[4]; + + if (SCULPT_has_colors(ss)) { + SCULPT_vertex_color_get(ss, vd.vertex, color); + + if (len_squared_v4v4(color, origcolor) > FLT_EPSILON) { + modified = true; + } + + SCULPT_vertex_color_set(ss, vd.vertex, origcolor); + } + } + } + BKE_pbvh_vertex_iter_end; + + if (modified && (type & undo::Type::Position) != undo::Type::None) { + BKE_pbvh_node_mark_update(node); + } } static void paint_mesh_restore_co(Sculpt *sd, Object *ob) @@ -1409,40 +1922,44 @@ static void paint_mesh_restore_co(Sculpt *sd, Object *ob) SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); - Vector nodes = bke::pbvh::search_gather(ss->pbvh, {}); + float radius = (brush->flag & BRUSH_ANCHORED) ? + 1.25 * max_ff(ss->cache->last_anchored_radius, ss->cache->radius) : + ss->cache->radius; - undo::Type type; - switch (brush->sculpt_tool) { + if (brush->flag & BRUSH_DRAG_DOT) { + radius = max_ff(radius, ss->cache->initial_radius) * 1.25f; + } + + Vector nodes = sculpt_pbvh_gather_generic(ob, brush, true, radius); + + undo::Type type = undo::Type::None; + + switch (SCULPT_get_tool(ss, brush)) { case SCULPT_TOOL_MASK: - type = undo::Type::Mask; + type |= undo::Type::Mask; break; case SCULPT_TOOL_PAINT: case SCULPT_TOOL_SMEAR: - type = undo::Type::Color; + type |= undo::Type::Color; break; case SCULPT_TOOL_DRAW_FACE_SETS: type = ss->cache->alt_smooth ? undo::Type::Position : undo::Type::FaceSet; break; default: - type = undo::Type::Position; + type |= undo::Type::Position; break; } - if (ss->bm) { - /* Disable multi-threading when dynamic-topology is enabled. Otherwise, - * new entries might be inserted by #undo::push_node() into the #GHash - * used internally by #BM_log_original_vert_co() by a different thread. See #33787. */ - for (const int i : nodes.index_range()) { - paint_mesh_restore_node(ob, type, nodes[i]); + SculptMaskWriteInfo mask_write; + if (type == undo::Type::Mask) { + mask_write = SCULPT_mask_get_for_write(ss); + } + + blender::threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { + for (const int i : range) { + paint_mesh_restore_node(ob, type, mask_write, nodes[i]); } - } - else { - threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { - for (const int i : range) { - paint_mesh_restore_node(ob, type, nodes[i]); - } - }); - } + }); if (type == undo::Type::Position) { /* Update normals for potentially-changed positions. Theoretically this may be unnecessary if @@ -1450,8 +1967,6 @@ static void paint_mesh_restore_co(Sculpt *sd, Object *ob) * know that from here. */ bke::pbvh::update_normals(*ss->pbvh, ss->subdiv_ccg); } - - BKE_pbvh_node_color_buffer_free(ss->pbvh); } } // namespace blender::ed::sculpt_paint @@ -1499,7 +2014,7 @@ bool SCULPT_get_redraw_rect(ARegion *region, RegionView3D *rv3d, Object *ob, rct /************************ Brush Testing *******************/ -void SCULPT_brush_test_init(SculptSession *ss, SculptBrushTest *test) +SculptBrushTestFn SCULPT_brush_test_init(const SculptSession *ss, SculptBrushTest *test) { using namespace blender; RegionView3D *rv3d = ss->cache ? ss->cache->vc->rv3d : ss->rv3d; @@ -1536,6 +2051,53 @@ void SCULPT_brush_test_init(SculptSession *ss, SculptBrushTest *test) else { test->clip_rv3d = nullptr; } + + test->falloff_shape = PAINT_FALLOFF_SHAPE_SPHERE; + return SCULPT_brush_test_sphere_sq; +} + +SculptBrushTestFn SCULPT_brush_test_init_ex(const SculptSession *ss, + SculptBrushTest *test, + char falloff_shape, + float tip_roundness, + float tip_scale_x) +{ + SCULPT_brush_test_init_with_falloff_shape(ss, test, falloff_shape); + + test->tip_roundness = tip_roundness; + test->tip_scale_x = tip_scale_x; + test->test_cube_z = true; + + /* XXX this is likely wrong. */ + if (ss->cache) { + copy_m4_m4(test->cube_matrix, ss->cache->brush_local_mat.ptr()); + } + else { + test->cube_matrix[0][0] = test->cube_matrix[1][1] = test->cube_matrix[2][2] = + test->cube_matrix[3][3] = 1.0f; + } + + mul_v3_fl(test->cube_matrix[0], tip_scale_x); + + return SCULPT_brush_test; +} + +bool SCULPT_brush_test(SculptBrushTest *test, const float co[3]) +{ + if (test->tip_roundness >= 1.0f) { + return SCULPT_brush_test_sphere_sq(test, co); + } + + bool ret = SCULPT_brush_test_cube(test, + co, + test->cube_matrix, + test->tip_roundness, + test->tip_scale_x, + test->falloff_shape != PAINT_FALLOFF_SHAPE_TUBE); + + test->dist *= test->dist; + + return ret; } BLI_INLINE bool sculpt_brush_test_clipping(const SculptBrushTest *test, const float co[3]) @@ -1588,7 +2150,8 @@ bool SCULPT_brush_test_cube(SculptBrushTest *test, const float co[3], const float local[4][4], const float roundness, - const float /*tip_scale_x*/) + const float /*tip_scale_x*/, + bool test_z) { float side = 1.0f; float local_co[3]; @@ -1610,7 +2173,7 @@ bool SCULPT_brush_test_cube(SculptBrushTest *test, const float constant_side = hardness * side; const float falloff_side = roundness * side; - if (!(local_co[0] <= side && local_co[1] <= side && local_co[2] <= side)) { + if (!(local_co[0] <= side && local_co[1] <= side && (local_co[2] <= side || !test_z))) { /* Outside the square. */ return false; } @@ -1632,7 +2195,7 @@ bool SCULPT_brush_test_cube(SculptBrushTest *test, return true; } -SculptBrushTestFn SCULPT_brush_test_init_with_falloff_shape(SculptSession *ss, +SculptBrushTestFn SCULPT_brush_test_init_with_falloff_shape(const SculptSession *ss, SculptBrushTest *test, char falloff_shape) { @@ -1640,6 +2203,8 @@ SculptBrushTestFn SCULPT_brush_test_init_with_falloff_shape(SculptSession *ss, falloff_shape = PAINT_FALLOFF_SHAPE_SPHERE; } + test->falloff_shape = falloff_shape; + SCULPT_brush_test_init(ss, test); SculptBrushTestFn sculpt_brush_test_sq_fn; if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { @@ -1655,7 +2220,7 @@ SculptBrushTestFn SCULPT_brush_test_init_with_falloff_shape(SculptSession *ss, return sculpt_brush_test_sq_fn; } -const float *SCULPT_brush_frontface_normal_from_falloff_shape(SculptSession *ss, +const float *SCULPT_brush_frontface_normal_from_falloff_shape(const SculptSession *ss, char falloff_shape) { if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { @@ -1815,8 +2380,8 @@ static void calc_area_normal_and_center_task(Object *ob, } SculptBrushTest normal_test; - SculptBrushTestFn sculpt_brush_normal_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &normal_test, brush->falloff_shape); + SculptBrushTestFn sculpt_brush_normal_test_sq_fn = SCULPT_brush_test_init_ex( + ss, &normal_test, (eBrushFalloffShape)brush->falloff_shape, 1.0f, 1.0f); /* Update the test radius to sample the normal using the normal radius of the brush. */ if (brush->ob_mode == OB_MODE_SCULPT) { @@ -1827,14 +2392,14 @@ static void calc_area_normal_and_center_task(Object *ob, } SculptBrushTest area_test; - SculptBrushTestFn sculpt_brush_area_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &area_test, brush->falloff_shape); + SculptBrushTestFn sculpt_brush_area_test_sq_fn = SCULPT_brush_test_init_ex( + ss, &area_test, (eBrushFalloffShape)brush->falloff_shape, 1.0f, 1.0f); if (brush->ob_mode == OB_MODE_SCULPT) { float test_radius = std::sqrt(area_test.radius_squared); /* Layer brush produces artifacts with normal and area radius */ /* Enable area radius control only on Scrape for now */ - if (ELEM(brush->sculpt_tool, SCULPT_TOOL_SCRAPE, SCULPT_TOOL_FILL) && + if (ELEM(SCULPT_get_tool(ss, brush), SCULPT_TOOL_SCRAPE, SCULPT_TOOL_FILL) && brush->area_radius_factor > 0.0f) { test_radius *= brush->area_radius_factor; @@ -1852,17 +2417,17 @@ static void calc_area_normal_and_center_task(Object *ob, /* When the mesh is edited we can't rely on original coords * (original mesh may not even have verts in brush radius). */ if (use_original && has_bm_orco) { - float(*orco_coords)[3]; - int(*orco_tris)[3]; - int orco_tris_num; + PBVHTriBuf *tribuf = BKE_pbvh_bmesh_get_tris(ss->pbvh, node); - BKE_pbvh_node_get_bm_orco_data(node, &orco_tris, &orco_tris_num, &orco_coords, nullptr); + for (PBVHTri &tri : tribuf->tris) { + PBVHVertRef v1 = tribuf->verts[tri.v[0]]; + PBVHVertRef v2 = tribuf->verts[tri.v[1]]; + PBVHVertRef v3 = tribuf->verts[tri.v[2]]; - for (int i = 0; i < orco_tris_num; i++) { const float *co_tri[3] = { - orco_coords[orco_tris[i][0]], - orco_coords[orco_tris[i][1]], - orco_coords[orco_tris[i][2]], + SCULPT_vertex_origco_get(ss, v1), + SCULPT_vertex_origco_get(ss, v2), + SCULPT_vertex_origco_get(ss, v3), }; float co[3]; @@ -1915,17 +2480,8 @@ static void calc_area_normal_and_center_task(Object *ob, float no_s[3]; if (use_original) { - if (unode->bm_entry) { - const float *temp_co; - const float *temp_no_s; - BM_log_original_vert_data(ss->bm_log, vd.bm_vert, &temp_co, &temp_no_s); - copy_v3_v3(co, temp_co); - copy_v3_v3(no_s, temp_no_s); - } - else { - copy_v3_v3(co, unode->position[vd.i]); - copy_v3_v3(no_s, unode->normal[vd.i]); - } + copy_v3_v3(co, vertex_attr_ptr(vd.vertex, ss->attrs.orig_co)); + copy_v3_v3(no_s, vertex_attr_ptr(vd.vertex, ss->attrs.orig_no)); } else { copy_v3_v3(co, vd.co); @@ -2011,7 +2567,7 @@ void SCULPT_calc_area_center(Sculpt *sd, Object *ob, Span nodes, flo using namespace blender::ed::sculpt_paint; const Brush *brush = BKE_paint_brush(&sd->paint); SculptSession *ss = ob->sculpt; - const bool has_bm_orco = ss->bm && dyntopo::stroke_is_dyntopo(ss, brush); + const bool has_bm_orco = ss->bm; int n; bool any_vertex_sampled = false; @@ -2063,7 +2619,7 @@ std::optional SCULPT_pbvh_calc_area_normal(const Brush *brush, using namespace blender; using namespace blender::ed::sculpt_paint; SculptSession *ss = ob->sculpt; - const bool has_bm_orco = ss->bm && dyntopo::stroke_is_dyntopo(ss, brush); + const bool has_bm_orco = ss->bm; bool any_vertex_sampled = false; @@ -2101,7 +2657,7 @@ void SCULPT_calc_area_normal_and_center( using namespace blender::ed::sculpt_paint; const Brush *brush = BKE_paint_brush(&sd->paint); SculptSession *ss = ob->sculpt; - const bool has_bm_orco = ss->bm && dyntopo::stroke_is_dyntopo(ss, brush); + const bool has_bm_orco = ss->bm; int n; bool any_vertex_sampled = false; @@ -2206,13 +2762,13 @@ static float brush_strength(const Sculpt *sd, return root_alpha * feather * pressure * overlap; } else if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_EXPAND) { - /* Expand is more sensible to strength as it keeps expanding the cloth when sculpting over - * the same vertices. */ + /* Expand is more sensible to strength as it keeps expanding the cloth when sculpting + * over the same vertices. */ return 0.1f * alpha * flip * pressure * overlap * feather; } else { - /* Multiply by 10 by default to get a larger range of strength depending on the size of the - * brush and object. */ + /* Multiply by 10 by default to get a larger range of strength depending on the size of + * the brush and object. */ return 10.0f * alpha * flip * pressure * overlap * feather; } case SCULPT_TOOL_DRAW_FACE_SETS: @@ -2666,12 +3222,15 @@ static void update_sculpt_normal(Sculpt *sd, Object *ob, Span nodes) { const Brush *brush = BKE_paint_brush(&sd->paint); StrokeCache *cache = ob->sculpt->cache; + int tool = SCULPT_get_tool(ob->sculpt, brush); + /* Grab brush does not update the sculpt normal during a stroke. */ - const bool update_normal = - !(brush->flag & BRUSH_ORIGINAL_NORMAL) && !(brush->sculpt_tool == SCULPT_TOOL_GRAB) && - !(brush->sculpt_tool == SCULPT_TOOL_THUMB && !(brush->flag & BRUSH_ANCHORED)) && - !(brush->sculpt_tool == SCULPT_TOOL_ELASTIC_DEFORM) && - !(brush->sculpt_tool == SCULPT_TOOL_SNAKE_HOOK && cache->normal_weight > 0.0f); + const bool update_normal = !((brush->flag & BRUSH_ORIGINAL_NORMAL) && + !(tool == SCULPT_TOOL_GRAB) && + !(tool == SCULPT_TOOL_THUMB && !(brush->flag & BRUSH_ANCHORED)) && + !(tool == SCULPT_TOOL_ELASTIC_DEFORM) && + !(tool == SCULPT_TOOL_SNAKE_HOOK && cache->normal_weight > 0.0f)) || + dot_v3v3(cache->sculpt_normal, cache->sculpt_normal) == 0.0f; if (cache->mirror_symmetry_pass == 0 && cache->radial_symmetry_pass == 0 && (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(cache) || update_normal)) @@ -2866,10 +3425,9 @@ struct SculptRaycastData { blender::VArraySpan hide_poly; PBVHVertRef active_vertex; + PBVHFaceRef active_face; float *face_normal; - int active_face_grid_index; - IsectRayPrecalc isect_precalc; }; @@ -3185,30 +3743,143 @@ void SCULPT_vertcos_to_key(Object *ob, KeyBlock *kb, const Span vertCos) BKE_keyblock_update_from_vertcos(ob, kb, reinterpret_cast(vertCos.data())); } -/* NOTE: we do the topology update before any brush actions to avoid +static void topology_undopush_cb(PBVHNode *node, void *data) +{ + SculptSearchSphereData *sdata = (SculptSearchSphereData *)data; + + undo::ensure_dyntopo_node_undo(sdata->ob, node, undo::Type::Position); +} + +int SCULPT_get_symmetry_pass(const SculptSession *ss) +{ + int symidx = ss->cache->mirror_symmetry_pass + (ss->cache->radial_symmetry_pass * 8); + + if (symidx >= SCULPT_MAX_SYMMETRY_PASSES) { + symidx = SCULPT_MAX_SYMMETRY_PASSES - 1; + } + + return symidx; +} + +struct DynTopoAutomaskState { + auto_mask::Cache *cache; + std::optional> unique_ptr; + SculptSession *ss; + auto_mask::Cache _fixed; + bool free_automasking; +}; + +static float sculpt_topology_automasking_cb(PBVHVertRef vertex, void *vdata) +{ + DynTopoAutomaskState *state = (DynTopoAutomaskState *)vdata; + + float mask = auto_mask::factor_get(state->cache, state->ss, vertex, nullptr); + float mask2 = 1.0f - SCULPT_vertex_mask_get(state->ss, vertex); + + return mask * mask2; +} + +static float sculpt_topology_automasking_mask_cb(PBVHVertRef vertex, void *vdata) +{ + DynTopoAutomaskState *state = (DynTopoAutomaskState *)vdata; + return 1.0f - SCULPT_vertex_mask_get(state->ss, vertex); +} + +static float sculpt_null_mask_cb(PBVHVertRef /*vertex*/, void * /*vdata*/) +{ + return 1.0f; +} + +bool SCULPT_dyntopo_automasking_init(const SculptSession *ss, + Sculpt *sd, + const Brush *br, + Object *ob, + blender::bke::dyntopo::DyntopoMaskCB *r_mask_cb, + void **r_mask_cb_data) +{ + if (!auto_mask::is_enabled(sd, ss, br)) { + if (CustomData_has_layer_named(&ss->bm->vdata, CD_PROP_FLOAT, ".sculpt_mask")) { + DynTopoAutomaskState *state = MEM_new("DynTopoAutomaskState"); + + if (!ss->cache) { + state->unique_ptr = auto_mask::cache_init(sd, br, ob); + state->cache = state->unique_ptr->get(); + } + else { + state->cache = ss->cache->automasking.get(); + } + + state->ss = (SculptSession *)ss; + + *r_mask_cb_data = (void *)state; + *r_mask_cb = sculpt_topology_automasking_mask_cb; + + return true; + } + else { + *r_mask_cb_data = nullptr; + *r_mask_cb = sculpt_null_mask_cb; + return false; + } + } + + DynTopoAutomaskState *state = (DynTopoAutomaskState *)MEM_callocN(sizeof(DynTopoAutomaskState), + "DynTopoAutomaskState"); + if (!ss->cache) { + state->unique_ptr = auto_mask::cache_init(sd, br, ob); + state->cache = state->unique_ptr->get(); + state->free_automasking = true; + } + else { + state->cache = ss->cache->automasking.get(); + } + + state->ss = (SculptSession *)ss; + + *r_mask_cb_data = (void *)state; + *r_mask_cb = sculpt_topology_automasking_cb; + + return true; +} + +void SCULPT_dyntopo_automasking_end(void *mask_data) +{ + DynTopoAutomaskState *state = static_cast(mask_data); + + if (state) { + MEM_delete(state); + } +} + +bool SCULPT_needs_area_normal(SculptSession * /*ss*/, Sculpt * /*sd*/, Brush *brush) +{ + return brush->tip_roundness != 1.0f || brush->tip_scale_x != 1.0f; +} + +/* Note: we do the topology update before any brush actions to avoid * issues with the proxies. The size of the proxy can't change, so * topology must be updated first. */ -static void sculpt_topology_update(Sculpt *sd, - Object *ob, - Brush *brush, - UnifiedPaintSettings * /*ups*/, - PaintModeSettings * /*paint_mode_settings*/) +static void sculpt_topology_update( + Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings * /* ups */, PaintModeSettings * + /*paint_mode_settings*/) { + using namespace blender::bke::dyntopo; using namespace blender; using namespace blender::ed::sculpt_paint; SculptSession *ss = ob->sculpt; - /* Build a list of all nodes that are potentially within the brush's area of influence. */ - const bool use_original = sculpt_tool_needs_original(brush->sculpt_tool) ? true : - !ss->cache->accum; - const float radius_scale = 1.25f; - Vector nodes = sculpt_pbvh_gather_generic(ob, brush, use_original, radius_scale); + /* build brush radius scale */ + float radius_scale = ss->cached_dyntopo.radius_scale; - /* Only act if some verts are inside the brush area. */ - if (nodes.is_empty()) { + if ((brush->dyntopo.flag & DYNTOPO_DISABLED) || !(sd->flags & SCULPT_DYNTOPO_ENABLED)) { return; } + /* Build a list of all nodes that are potentially within the brush's area of influence. */ + const bool use_original = sculpt_tool_needs_original(SCULPT_get_tool(ss, brush)) ? + true : + !ss->cache->accum; + /* Free index based vertex info as it will become invalid after modifying the topology during the * stroke. */ ss->vertex_info.boundary.clear(); @@ -3216,42 +3887,137 @@ static void sculpt_topology_update(Sculpt *sd, PBVHTopologyUpdateMode mode = PBVHTopologyUpdateMode(0); float location[3]; - if (!(sd->flags & SCULPT_DYNTOPO_DETAIL_MANUAL)) { - if (sd->flags & SCULPT_DYNTOPO_SUBDIVIDE) { + int dyntopo_mode = ss->cached_dyntopo.flag; + int dyntopo_detail_mode = ss->cached_dyntopo.mode; + + if (dyntopo_detail_mode != DYNTOPO_DETAIL_MANUAL) { + if (dyntopo_mode & DYNTOPO_SUBDIVIDE) { mode |= PBVH_Subdivide; } + else if (dyntopo_mode & DYNTOPO_LOCAL_SUBDIVIDE) { + mode |= PBVH_LocalSubdivide | PBVH_Subdivide; + } - if ((sd->flags & SCULPT_DYNTOPO_COLLAPSE) || (brush->sculpt_tool == SCULPT_TOOL_SIMPLIFY)) { + if (dyntopo_mode & DYNTOPO_COLLAPSE) { + mode |= PBVH_Collapse; + } + else if (dyntopo_mode & DYNTOPO_LOCAL_COLLAPSE) { + mode |= PBVH_LocalCollapse | PBVH_Collapse; + } + } + else { + if (dyntopo_mode & DYNTOPO_SUBDIVIDE) { + mode |= PBVH_Subdivide; + } + if (dyntopo_mode & DYNTOPO_COLLAPSE) { mode |= PBVH_Collapse; } } - for (PBVHNode *node : nodes) { - undo::push_node(ob, - node, - brush->sculpt_tool == SCULPT_TOOL_MASK ? undo::Type::Mask : - undo::Type::Position); - BKE_pbvh_node_mark_update(node); + if (dyntopo_mode & DYNTOPO_CLEANUP) { + mode |= PBVH_Cleanup; + } - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - BKE_pbvh_node_mark_topology_update(node); - BKE_pbvh_bmesh_node_save_orig(ss->bm, ss->bm_log, node, false); + SculptSearchSphereData sdata{}; + sdata.ss = ss, sdata.sd = sd, sdata.ob = ob; + sdata.radius_squared = square_f(ss->cache->radius * radius_scale * 1.25f); + sdata.original = use_original; + sdata.ignore_fully_ineffective = SCULPT_get_tool(ss, brush) != SCULPT_TOOL_MASK; + sdata.center = nullptr; + sdata.brush = brush; + + void *mask_cb_data; + blender::bke::dyntopo::DyntopoMaskCB mask_cb; + + BKE_pbvh_set_bm_log(ss->pbvh, ss->bm_log); + + SCULPT_dyntopo_automasking_init(ss, sd, brush, ob, &mask_cb, &mask_cb_data); + + int actv = BM_ID_NONE, actf = BM_ID_NONE; + + if (ss->active_vertex.i != PBVH_REF_NONE) { + BM_idmap_check_assign(ss->bm_idmap, (BMVert *)ss->active_vertex.i); + actv = BM_idmap_get_id(ss->bm_idmap, (BMVert *)ss->active_vertex.i); + } + + if (ss->active_face.i != PBVH_REF_NONE) { + BM_idmap_check_assign(ss->bm_idmap, (BMFace *)ss->active_face.i); + actf = BM_idmap_get_id(ss->bm_idmap, (BMFace *)ss->active_face.i); + } + + blender::bke::dyntopo::BrushSphere sphere_tester(ss->cache->location, + ss->cache->radius * radius_scale); + blender::bke::dyntopo::BrushTube tube_tester( + ss->cache->location, ss->cache->view_normal, ss->cache->radius * radius_scale); + + float radius = ss->cache->radius * radius_scale; + + /* do nodes under the brush cursor */ + blender::bke::dyntopo::remesh_topology_nodes( + brush->falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE ? &sphere_tester : &tube_tester, + ob, + ss->pbvh, + node_in_sphere, + topology_undopush_cb, + ss->cache->location, + radius * radius, + sdata.original, + mode, + (brush->flag & BRUSH_FRONTFACE) != 0, + ss->cache->view_normal, + true, + mask_cb, + mask_cb_data, + ss->cached_dyntopo.quality, + static_cast(&sdata)); + + SCULPT_dyntopo_automasking_end(mask_cb_data); + + if (actv != BM_ID_NONE) { + BMVert *v = BM_idmap_lookup(ss->bm_idmap, actv); + + if (v && v->head.htype == BM_VERT) { + ss->active_vertex.i = (intptr_t)v; + } + else { + ss->active_vertex.i = PBVH_REF_NONE; } } - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - bke::pbvh::bmesh_update_topology(ss->pbvh, - mode, - ss->cache->location, - ss->cache->view_normal, - ss->cache->radius, - (brush->flag & BRUSH_FRONTFACE) != 0, - (brush->falloff_shape != PAINT_FALLOFF_SHAPE_SPHERE)); + if (actf != BM_ID_NONE) { + BMFace *f = BM_idmap_lookup(ss->bm_idmap, actf); + + if (f && f->head.htype == BM_FACE) { + ss->active_face.i = (intptr_t)f; + } + else { + ss->active_face.i = PBVH_REF_NONE; + } } /* Update average stroke position. */ copy_v3_v3(location, ss->cache->true_location); mul_m4_v3(ob->object_to_world().ptr(), location); + + ss->totfaces = ss->faces_num = ss->bm->totface; + ss->totvert = ss->bm->totvert; +} + +static void do_check_origco(Object *ob, PBVHNode *node) +{ + SculptSession *ss = ob->sculpt; + PBVHVertexIter vd; + + bool modified = false; + + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { + modified |= SCULPT_vertex_check_origdata(ss, vd.vertex); + } + BKE_pbvh_vertex_iter_end; + + if (modified) { + BKE_pbvh_node_mark_original_update(node); + } } static void do_brush_action_task(Object *ob, const Brush *brush, PBVHNode *node) @@ -3305,16 +4071,18 @@ static void do_brush_action(Sculpt *sd, PBVHType type = BKE_pbvh_type(ss->pbvh); if (SCULPT_tool_is_paint(brush->sculpt_tool) && SCULPT_has_loop_colors(ob)) { - if (type != PBVH_FACES) { + if (type == PBVH_GRIDS) { return; } - - BKE_pbvh_ensure_node_loops(ss->pbvh); + else if (type == PBVH_FACES) { + BKE_pbvh_ensure_node_loops(ss->pbvh); + } } - const bool use_original = sculpt_tool_needs_original(brush->sculpt_tool) ? true : - !ss->cache->accum; + const bool use_original = sculpt_tool_needs_original(brush->sculpt_tool) || !ss->cache->accum; const bool use_pixels = sculpt_needs_pbvh_pixels(paint_mode_settings, brush, ob); + bool needs_original = use_original || auto_mask::needs_original(sd, brush); + needs_original |= bool(brush->flag & (BRUSH_ANCHORED | BRUSH_DRAG_DOT)); if (sculpt_needs_pbvh_pixels(paint_mode_settings, brush, ob)) { sculpt_pbvh_update_pixels(paint_mode_settings, ss, ob); @@ -3384,26 +4152,92 @@ static void do_brush_action(Sculpt *sd, if ((brush->sculpt_tool == SCULPT_TOOL_SMOOTH) && (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_SURFACE)) { - BLI_assert(ss->cache->surface_smooth_laplacian_disp == nullptr); - ss->cache->surface_smooth_laplacian_disp = static_cast( - MEM_callocN(sizeof(float[3]) * SCULPT_vertex_count_get(ss), "HC smooth laplacian b")); + smooth::surface_smooth_laplacian_init(ob); } } } + if (!ss->cache->accum || needs_original) { + threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { + for (const int i : range) { + do_check_origco(ob, nodes[i]); + } + }); + blender::bke::pbvh::update_bounds(*ss->pbvh, PBVH_UpdateOriginalBB); + } + /* Only act if some verts are inside the brush area. */ if (nodes.is_empty()) { return; } float location[3]; - if (!use_pixels) { + if (!use_pixels && !ss->bm) { threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { for (const int i : range) { do_brush_action_task(ob, brush, nodes[i]); } }); } + else if (ss->bm) { + undo::Type undo_type; + undo::Type extra_type = undo::Type::None; + + if (SCULPT_tool_is_paint(brush->sculpt_tool)) { + undo_type = undo::Type::Color; + } + else if (brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS) { + if (ss->cache->alt_smooth) { + undo_type = undo::Type::Position; + } + else { + undo_type = undo::Type::FaceSet; + } + } + else if (brush->sculpt_tool == SCULPT_TOOL_MASK) { + undo_type = undo::Type::Mask; + } + else { + undo_type = undo::Type::Position; + } + + if (ss->cache->supports_gravity && sd->gravity_factor > 0.0f && + undo_type != undo::Type::Position) + { + extra_type = undo::Type::Position; + } + + for (PBVHNode *node : nodes) { + undo::ensure_dyntopo_node_undo(ob, node, undo_type, extra_type); + + switch (undo_type) { + case undo::Type::FaceSet: + BKE_pbvh_node_mark_update_face_sets(node); + break; + case undo::Type::Mask: + BKE_pbvh_node_mark_update_mask(node); + break; + case undo::Type::Color: + BKE_pbvh_node_mark_update_color(node); + break; + case undo::Type::Position: + BKE_pbvh_node_mark_update(node); + break; + case undo::Type::HideVert: + case undo::Type::HideFace: + case undo::Type::DyntopoBegin: + case undo::Type::DyntopoEnd: + case undo::Type::DyntopoSymmetrize: + case undo::Type::Geometry: + case undo::Type::None: + break; + } + + if (extra_type == undo::Type::Position) { + BKE_pbvh_node_mark_update(node); + } + } + } if (sculpt_brush_needs_normal(ss, sd, brush)) { update_sculpt_normal(sd, ob, nodes); @@ -3437,7 +4271,7 @@ static void do_brush_action(Sculpt *sd, break; case SCULPT_TOOL_SMOOTH: if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_LAPLACIAN) { - smooth::do_smooth_brush(sd, ob, nodes); + smooth::do_smooth_brush(sd, ob, nodes, ss->cache->bstrength, 0.0f); } else if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_SURFACE) { smooth::do_surface_smooth_brush(sd, ob, nodes); @@ -3535,7 +4369,7 @@ static void do_brush_action(Sculpt *sd, SCULPT_do_displacement_smear_brush(sd, ob, nodes); break; case SCULPT_TOOL_PAINT: - color::do_paint_brush(paint_mode_settings, sd, ob, nodes, texnodes); + color::do_paint_brush(paint_mode_settings, ss->cache->vc->scene, sd, ob, nodes, texnodes); break; case SCULPT_TOOL_SMEAR: color::do_smear_brush(sd, ob, nodes); @@ -3618,12 +4452,6 @@ static void sculpt_combine_proxies_node(Object &object, using namespace blender::ed::sculpt_paint; SculptSession *ss = object.sculpt; - float(*orco)[3] = nullptr; - if (use_orco && !ss->bm) { - orco = reinterpret_cast( - (undo::push_node(&object, &node, undo::Type::Position)->position.data())); - } - MutableSpan proxies = BKE_pbvh_node_get_proxies(&node); Mesh &mesh = *static_cast(object.data); @@ -3633,27 +4461,30 @@ static void sculpt_combine_proxies_node(Object &object, BKE_pbvh_vertex_iter_begin (ss->pbvh, &node, vd, PBVH_ITER_UNIQUE) { float val[3]; - if (use_orco) { - if (ss->bm) { - copy_v3_v3(val, BM_log_original_vert_co(ss->bm_log, vd.bm_vert)); - } - else { - copy_v3_v3(val, orco[vd.i]); - } - } - else { - copy_v3_v3(val, vd.co); - } + zero_v3(val); for (const PBVHProxyNode &proxy_node : proxies) { add_v3_v3(val, proxy_node.co[vd.i]); } + bool modified = len_squared_v3(val) > 0.0f; + + if (use_orco) { + add_v3_v3(val, SCULPT_vertex_origco_get(ss, vd.vertex)); + } + else { + add_v3_v3(val, vd.co); + } + SCULPT_clip(&sd, ss, vd.co, val); if (ss->deform_modifiers_active) { sculpt_flush_pbvhvert_deform(*ss, vd, positions); } + + if (modified) { + BKE_sculpt_sharp_boundary_flag_update(ss, vd.vertex); + } } BKE_pbvh_vertex_iter_end; @@ -3725,6 +4556,10 @@ void SCULPT_flush_stroke_deform(Sculpt * /*sd*/, Object *ob, bool is_proxy_used) using namespace blender; SculptSession *ss = ob->sculpt; + if (ob->sculpt->bm) { + return; + } + if (is_proxy_used && ss->deform_modifiers_active) { /* This brushes aren't using proxies, so sculpt_combine_proxies() wouldn't propagate needed * deformation to original base. */ @@ -4059,10 +4894,6 @@ void SCULPT_cache_free(blender::ed::sculpt_paint::StrokeCache *cache) { using namespace blender::ed::sculpt_paint; MEM_SAFE_FREE(cache->dial); - MEM_SAFE_FREE(cache->surface_smooth_laplacian_disp); - MEM_SAFE_FREE(cache->layer_displacement_factor); - MEM_SAFE_FREE(cache->prev_colors); - MEM_SAFE_FREE(cache->detail_directions); MEM_SAFE_FREE(cache->prev_displacement); MEM_SAFE_FREE(cache->limit_surface_co); @@ -4211,6 +5042,11 @@ static void sculpt_update_cache_invariants( float max_scale; int mode; + ss->hard_edge_mode = ups->hard_edge_mode; + ss->smooth_boundary_flag = eSculptBoundary(ups->smooth_boundary_flag); + + BKE_sculpt_distort_correction_set(ob, eAttrCorrectMode(ups->distort_correction_mode)); + ss->cache = cache; /* Set scaling adjustment. */ @@ -4351,6 +5187,22 @@ static void sculpt_update_cache_invariants( } #undef PIXEL_INPUT_THRESHHOLD + + if (ss->pbvh) { + /* NotForPR: draw original coordinates for debugging. */ + BKE_pbvh_show_orig_set(ss->pbvh, tool_settings->show_origco); + } + + if (SCULPT_tool_is_paint(brush->sculpt_tool)) { + BKE_sculpt_ensure_origcolor(ob); + } + else if (SCULPT_tool_is_mask(brush->sculpt_tool)) { + BKE_sculpt_ensure_origmask(ob); + } + + dyntopo::apply_settings(CTX_data_scene(C), ss, sd, brush); + + blender::bke::pbvh::update_bounds(*ss->pbvh, PBVH_UpdateBB | PBVH_UpdateOriginalBB); } static float sculpt_brush_dynamic_size_get(Brush *brush, @@ -4371,8 +5223,8 @@ static float sculpt_brush_dynamic_size_get(Brush *brush, } } -/* In these brushes the grab delta is calculated always from the initial stroke location, which is - * generally used to create grab deformations. */ +/* In these brushes the grab delta is calculated always from the initial stroke location, which + * is generally used to create grab deformations. */ static bool sculpt_needs_delta_from_anchored_origin(Brush *brush) { if (brush->sculpt_tool == SCULPT_TOOL_SMEAR && (brush->flag & BRUSH_ANCHORED)) { @@ -4596,9 +5448,9 @@ static void sculpt_update_cache_paint_variants(blender::ed::sculpt_paint::Stroke 1.0f - cache->pressure : cache->pressure; - /* This makes wet mix more sensible in higher values, which allows to create brushes that have - * a wider pressure range were they only blend colors without applying too much of the brush - * color. */ + /* This makes wet mix more sensible in higher values, which allows to create brushes that + * have a wider pressure range were they only blend colors without applying too much of the + * brush color. */ cache->paint_brush.wet_mix = 1.0f - pow2f(1.0f - cache->paint_brush.wet_mix); } @@ -4676,33 +5528,40 @@ static void sculpt_update_cache_variants(bContext *C, Sculpt *sd, Object *ob, Po } } - if (BKE_brush_use_size_pressure(brush) && paint_supports_dynamic_size(brush, PaintMode::Sculpt)) - { - cache->radius = sculpt_brush_dynamic_size_get(brush, cache, cache->initial_radius); - cache->dyntopo_pixel_radius = sculpt_brush_dynamic_size_get( - brush, cache, ups->initial_pixel_radius); - } - else { - cache->radius = cache->initial_radius; - cache->dyntopo_pixel_radius = ups->initial_pixel_radius; - } - sculpt_update_cache_paint_variants(cache, brush); - cache->radius_squared = cache->radius * cache->radius; - if (brush->flag & BRUSH_ANCHORED) { /* True location has been calculated as part of the stroke system already here. */ if (brush->flag & BRUSH_EDGE_TO_EDGE) { RNA_float_get_array(ptr, "location", cache->true_location); } + cache->last_anchored_radius = cache->radius; + cache->radius = paint_calc_object_space_radius( cache->vc, cache->true_location, ups->pixel_radius); - cache->radius_squared = cache->radius * cache->radius; copy_v3_v3(cache->anchored_location, cache->true_location); } + else if (BKE_brush_use_size_pressure(brush) && + paint_supports_dynamic_size(brush, PaintMode::Sculpt)) + { + cache->radius = sculpt_brush_dynamic_size_get(brush, cache, cache->initial_radius); + } + else { + cache->radius = cache->initial_radius; + } + + if (BKE_brush_use_size_pressure(brush) && paint_supports_dynamic_size(brush, PaintMode::Sculpt)) + { + cache->dyntopo_pixel_radius = sculpt_brush_dynamic_size_get( + brush, cache, ups->initial_pixel_radius); + } + else { + cache->dyntopo_pixel_radius = ups->initial_pixel_radius; + } + + cache->radius_squared = cache->radius * cache->radius; sculpt_update_brush_delta(ups, ob, brush); @@ -4786,7 +5645,10 @@ static void sculpt_raycast_cb(PBVHNode &node, SculptRaycastData &srd, float *tmi } } - if (bke::pbvh::raycast_node(srd.ss->pbvh, + int hit_count = 0; + + if (bke::pbvh::raycast_node(srd.ss, + srd.ss->pbvh, &node, origco, use_origco, @@ -4795,10 +5657,12 @@ static void sculpt_raycast_cb(PBVHNode &node, SculptRaycastData &srd, float *tmi srd.ray_start, srd.ray_normal, &srd.isect_precalc, + &hit_count, &srd.depth, &srd.active_vertex, - &srd.active_face_grid_index, - srd.face_normal)) + &srd.active_face, + srd.face_normal, + srd.ss->stroke_id)) { srd.hit = true; *tmin = srd.depth; @@ -4829,16 +5693,16 @@ static void sculpt_find_nearest_to_ray_cb(PBVHNode &node, } } - if (bke::pbvh::find_nearest_to_ray_node(srd.ss->pbvh, + if (bke::pbvh::find_nearest_to_ray_node(srd.ss, + srd.ss->pbvh, &node, origco, use_origco, - srd.corner_verts, - srd.hide_poly, srd.ray_start, srd.ray_normal, &srd.depth, - &srd.dist_sq_to_ray)) + &srd.dist_sq_to_ray, + srd.ss->stroke_id)) { srd.hit = true; *tmin = srd.dist_sq_to_ray; @@ -4937,6 +5801,8 @@ bool SCULPT_cursor_geometry_info_update(bContext *C, srd.ray_normal = ray_normal; srd.depth = depth; srd.face_normal = face_normal; + srd.active_face.i = PBVH_REF_NONE; + srd.active_vertex.i = PBVH_REF_NONE; isect_ray_tri_watertight_v3_precalc(&srd.isect_precalc, ray_normal); bke::pbvh::raycast( @@ -4944,7 +5810,8 @@ bool SCULPT_cursor_geometry_info_update(bContext *C, [&](PBVHNode &node, float *tmin) { sculpt_raycast_cb(node, srd, tmin); }, ray_start, ray_normal, - srd.original); + srd.original, + ss->stroke_id); /* Cursor is not over the mesh, return default values. */ if (!srd.hit) { @@ -4956,20 +5823,19 @@ bool SCULPT_cursor_geometry_info_update(bContext *C, /* Update the active vertex of the SculptSession. */ ss->active_vertex = srd.active_vertex; - SCULPT_vertex_random_access_ensure(ss); copy_v3_v3(out->active_vertex_co, SCULPT_active_vertex_co_get(ss)); switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: - ss->active_face_index = srd.active_face_grid_index; + ss->active_face = srd.active_face; ss->active_grid_index = 0; break; case PBVH_GRIDS: - ss->active_face_index = 0; - ss->active_grid_index = srd.active_face_grid_index; + ss->active_face = srd.active_face; + ss->active_grid_index = ss->active_face.i; break; case PBVH_BMESH: - ss->active_face_index = 0; + ss->active_face = srd.active_face; ss->active_grid_index = 0; break; } @@ -5069,11 +5935,6 @@ bool SCULPT_stroke_get_location_ex(bContext *C, depth = SCULPT_raycast_init(&vc, mval, ray_start, ray_end, ray_normal, original); - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - BM_mesh_elem_table_ensure(ss->bm, BM_VERT); - BM_mesh_elem_index_ensure(ss->bm, BM_VERT); - } - bool hit = false; { SculptRaycastData srd; @@ -5090,6 +5951,8 @@ bool SCULPT_stroke_get_location_ex(bContext *C, srd.depth = depth; srd.original = original; srd.face_normal = face_normal; + srd.active_face.i = PBVH_REF_NONE; + srd.active_vertex.i = PBVH_REF_NONE; isect_ray_tri_watertight_v3_precalc(&srd.isect_precalc, ray_normal); bke::pbvh::raycast( @@ -5097,7 +5960,8 @@ bool SCULPT_stroke_get_location_ex(bContext *C, [&](PBVHNode &node, float *tmin) { sculpt_raycast_cb(node, srd, tmin); }, ray_start, ray_normal, - srd.original); + srd.original, + ss->stroke_id); if (srd.hit) { hit = true; copy_v3_v3(out, ray_normal); @@ -5192,7 +6056,6 @@ static void sculpt_brush_stroke_init(bContext *C) static void sculpt_restore_mesh(Sculpt *sd, Object *ob) { using namespace blender::ed::sculpt_paint; - SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); /* For the cloth brush it makes more sense to not restore the mesh state to keep running the @@ -5202,17 +6065,15 @@ static void sculpt_restore_mesh(Sculpt *sd, Object *ob) } /* Restore the mesh before continuing with anchored stroke. */ - if ((brush->flag & BRUSH_ANCHORED) || - (ELEM(brush->sculpt_tool, SCULPT_TOOL_GRAB, SCULPT_TOOL_ELASTIC_DEFORM) && - BKE_brush_use_size_pressure(brush)) || - (brush->flag & BRUSH_DRAG_DOT)) - { - + if (is_realtime_restored(brush)) { paint_mesh_restore_co(sd, ob); + } +} - if (ss->cache) { - MEM_SAFE_FREE(ss->cache->layer_displacement_factor); - } +void SCULPT_update_object_bounding_box(Object *ob) +{ + if (ob->runtime->bounds_eval) { + ob->runtime->bounds_eval = BKE_pbvh_bounding_box(ob->sculpt->pbvh); } } @@ -5241,8 +6102,8 @@ void SCULPT_flush_update_step(bContext *C, SculptUpdateType update_flags) if ((update_flags & SCULPT_UPDATE_IMAGE) != 0) { ED_region_tag_redraw(region); if (update_flags == SCULPT_UPDATE_IMAGE) { - /* Early exit when only need to update the images. We don't want to tag any geometry updates - * that would rebuilt the PBVH. */ + /* Early exit when only need to update the images. We don't want to tag any geometry + * updates that would rebuilt the PBVH. */ return; } } @@ -5263,7 +6124,7 @@ void SCULPT_flush_update_step(bContext *C, SculptUpdateType update_flags) rcti r; if (update_flags & SCULPT_UPDATE_COORDS) { - bke::pbvh::update_bounds(*ss->pbvh, PBVH_UpdateBB); + bke::pbvh::update_bounds(*ss->pbvh, int(PBVH_UpdateBB)); } RegionView3D *rv3d = CTX_wm_region_view3d(C); @@ -5375,13 +6236,13 @@ void SCULPT_flush_update_done(const bContext *C, Object *ob, SculptUpdateType up bke::pbvh::update_mask(*ss->pbvh); } + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + blender::bke::dyntopo::after_stroke(ss->pbvh, false); + } + BKE_sculpt_attributes_destroy_temporary_stroke(ob); if (update_flags & SCULPT_UPDATE_COORDS) { - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - BKE_pbvh_bmesh_after_stroke(ss->pbvh); - } - /* Optimization: if there is locked key and active modifiers present in */ /* the stack, keyblock is updating at each step. otherwise we could update */ /* keyblock only when stroke is finished. */ @@ -5450,8 +6311,7 @@ bool SCULPT_handles_colors_report(SculptSession *ss, ReportList *reports) case PBVH_FACES: return true; case PBVH_BMESH: - BKE_report(reports, RPT_ERROR, "Not supported in dynamic topology mode"); - return false; + return true; case PBVH_GRIDS: BKE_report(reports, RPT_ERROR, "Not supported in multiresolution mode"); return false; @@ -5477,8 +6337,8 @@ static bool sculpt_stroke_test_start(bContext *C, wmOperator *op, const float mv Brush *brush = BKE_paint_brush(&sd->paint); ToolSettings *tool_settings = CTX_data_tool_settings(C); - /* NOTE: This should be removed when paint mode is available. Paint mode can force based on the - * canvas it is painting on. (ref. use_sculpt_texture_paint). */ + /* NOTE: This should be removed when paint mode is available. Paint mode can force based on + * the canvas it is painting on. (ref. use_sculpt_texture_paint). */ if (brush && SCULPT_tool_is_paint(brush->sculpt_tool) && !SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { @@ -5492,19 +6352,58 @@ static bool sculpt_stroke_test_start(bContext *C, wmOperator *op, const float mv sculpt_update_cache_invariants(C, sd, ss, op, mval); + SCULPT_stroke_id_next(ob); + ss->cache->stroke_id = ss->stroke_id; + + if (ss->pbvh) { + blender::bke::pbvh::on_stroke_start(ss->pbvh); + } + SculptCursorGeometryInfo sgi; SCULPT_cursor_geometry_info_update(C, &sgi, mval, false); sculpt_stroke_undo_begin(C, op); - SCULPT_stroke_id_next(ob); - ss->cache->stroke_id = ss->stroke_id; - return true; } return false; } +void face_mark_boundary_update(SculptSession *ss, PBVHFaceRef face) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_BMESH: { + BMFace *f = reinterpret_cast(face.i); + BMLoop *l = f->l_first; + do { + PBVHVertRef vertex = {reinterpret_cast(l->v)}; + BKE_sculpt_boundary_flag_update(ss, vertex, true); + } while ((l = l->next) != f->l_first); + + break; + } + case PBVH_FACES: { + for (int vert_i : ss->corner_verts.slice(ss->faces[face.i])) { + PBVHVertRef vertex = {vert_i}; + BKE_sculpt_boundary_flag_update(ss, vertex, true); + } + break; + case PBVH_GRIDS: { + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + int grid_index = BKE_subdiv_ccg_start_face_grid_index_get(*ss->subdiv_ccg)[face.i]; + int vertex_i = grid_index * key->grid_area; + int verts_num = ss->faces[face.i].size() * key->grid_area; + + for (int i = 0; i < verts_num; i++, vertex_i++) { + BKE_sculpt_boundary_flag_update(ss, {vertex_i}, true); + } + + break; + } + } + } +} + static void sculpt_stroke_update_step(bContext *C, wmOperator * /*op*/, PaintStroke *stroke, @@ -5517,35 +6416,106 @@ static void sculpt_stroke_update_step(bContext *C, const Brush *brush = BKE_paint_brush(&sd->paint); ToolSettings *tool_settings = CTX_data_tool_settings(C); StrokeCache *cache = ss->cache; - cache->stroke_distance = paint_stroke_distance_get(stroke); + + float stroke_distance = paint_stroke_distance_get(stroke); + float stroke_delta = stroke_distance - cache->stroke_distance; + cache->stroke_distance = stroke_distance; SCULPT_stroke_modifiers_check(C, ob, brush); sculpt_update_cache_variants(C, sd, ob, itemptr); sculpt_restore_mesh(sd, ob); - if (sd->flags & (SCULPT_DYNTOPO_DETAIL_CONSTANT | SCULPT_DYNTOPO_DETAIL_MANUAL)) { - BKE_pbvh_bmesh_detail_size_set( - ss->pbvh, dyntopo::detail_size::constant_to_detail_size(sd->constant_detail, ob)); - } - else if (sd->flags & SCULPT_DYNTOPO_DETAIL_BRUSH) { - BKE_pbvh_bmesh_detail_size_set( - ss->pbvh, - dyntopo::detail_size::brush_to_detail_size(sd->detail_percent, ss->cache->radius)); + if (brush->flag & BRUSH_SCENE_SPACING) { + stroke_delta /= ss->cache->radius; } else { - BKE_pbvh_bmesh_detail_size_set( - ss->pbvh, - dyntopo::detail_size::relative_to_detail_size( - sd->detail_size, ss->cache->radius, ss->cache->dyntopo_pixel_radius, U.pixelsize)); + stroke_delta /= ups->pixel_radius; + } + cache->stroke_distance_t += stroke_delta; + + float radius = ss->cache->radius; + if (brush->flag & BRUSH_ANCHORED) { + radius = sculpt_calc_radius(cache->vc, brush, CTX_data_scene(C), cache->true_location); } - if (dyntopo::stroke_is_dyntopo(ss, brush)) { - do_symmetrical_brush_actions(sd, ob, sculpt_topology_update, ups, &tool_settings->paint_mode); + if (ELEM(ss->cached_dyntopo.mode, DYNTOPO_DETAIL_CONSTANT, DYNTOPO_DETAIL_MANUAL)) { + float object_space_constant_detail = 1.0f / (ss->cached_dyntopo.constant_detail * + mat4_to_scale(ob->object_to_world().ptr())); + blender::bke::dyntopo::detail_size_set( + ss->pbvh, object_space_constant_detail, DYNTOPO_DETAIL_RANGE); + } + else if (ss->cached_dyntopo.mode == DYNTOPO_DETAIL_BRUSH) { + blender::bke::dyntopo::detail_size_set( + ss->pbvh, radius * ss->cached_dyntopo.detail_percent / 100.0f, DYNTOPO_DETAIL_RANGE); + } + else { /* Relative mode. */ + blender::bke::dyntopo::detail_size_set(ss->pbvh, + (radius / ss->cache->dyntopo_pixel_radius) * + (ss->cached_dyntopo.detail_size * U.pixelsize), + DYNTOPO_DETAIL_RANGE); + } + + float dyntopo_spacing = float(ss->cached_dyntopo.spacing) / 50.0f; + + bool do_dyntopo = dyntopo::stroke_is_dyntopo(ss, sd, brush); + + bool has_spacing = !(brush->flag & BRUSH_ANCHORED); + if (has_spacing && dyntopo_spacing > 0.0f) { + do_dyntopo = do_dyntopo && + (ss->cache->stroke_distance_t == 0.0f || + ss->cache->stroke_distance_t - ss->cache->last_dyntopo_t > dyntopo_spacing); + } + + /* Have to initialize automasking here since dyntopo might invalidate the active face. */ + if (SCULPT_stroke_is_first_brush_step(ss->cache)) { + /* Initialize auto-masking cache. */ + if (auto_mask::is_enabled(sd, ss, brush)) { + ss->cache->automasking = auto_mask::cache_init(sd, brush, ob); + ss->last_automasking_settings_hash = auto_mask::settings_hash(*ob, *ss->cache->automasking); + } + } + + /* Running dyntopo before layer brush causes artifacts. */ + bool run_dyntopo_after = brush->sculpt_tool == SCULPT_TOOL_LAYER; + + if (do_dyntopo) { + ss->cache->last_dyntopo_t = ss->cache->stroke_distance_t; + + if (!run_dyntopo_after) { + /* Note: dyntopo repeats happen after the dab. */ + do_symmetrical_brush_actions( + sd, ob, sculpt_topology_update, ups, &tool_settings->paint_mode); + } } do_symmetrical_brush_actions(sd, ob, do_brush_action, ups, &tool_settings->paint_mode); sculpt_combine_proxies(sd, ob); + int dyntopo_repeat = ss->cached_dyntopo.repeat; + if (run_dyntopo_after) { + dyntopo_repeat++; + } + + if (do_dyntopo && dyntopo_repeat) { + float3 location = ss->cache->true_location; + + add_v3_v3(cache->true_location, cache->grab_delta); + + for (int i = 0; i < dyntopo_repeat; i++) { + do_symmetrical_brush_actions( + sd, ob, sculpt_topology_update, ups, &tool_settings->paint_mode); + } + + copy_v3_v3(ss->cache->true_location, location); + } + + if (ss->bm && do_dyntopo) { + if (ss->cache->stroke_distance_t - ss->cache->last_dyntopo_nodesplit_t > 0.3f) { + ss->cache->last_dyntopo_nodesplit_t = ss->cache->stroke_distance_t; + blender::bke::pbvh::split_bmesh_nodes(ss->pbvh); + } + } + /* Hack to fix noise texture tearing mesh. */ sculpt_fix_noise_tear(sd, ob); @@ -5622,7 +6592,6 @@ static void sculpt_stroke_done(const bContext *C, PaintStroke * /*stroke*/) brush = BKE_paint_brush(&sd->paint); } - BKE_pbvh_node_color_buffer_free(ss->pbvh); SCULPT_cache_free(ss->cache); ss->cache = nullptr; @@ -5747,8 +6716,11 @@ static void sculpt_brush_stroke_cancel(bContext *C, wmOperator *op) const Brush *brush = BKE_paint_brush(&sd->paint); /* XXX Canceling strokes that way does not work with dynamic topology, - * user will have to do real undo for now. See #46456. */ - if (ss->cache && !dyntopo::stroke_is_dyntopo(ss, brush)) { + * user will have to do real undo for now. See #46456. + * + * Update: this may actually work now. Test. + */ + if (ss->cache && !dyntopo::stroke_is_dyntopo(ss, sd, brush)) { paint_mesh_restore_co(sd, ob); } @@ -5789,7 +6761,7 @@ void SCULPT_OT_brush_stroke(wmOperatorType *ot) /* Properties. */ - paint_stroke_operator_properties(ot); + paint_stroke_operator_properties(ot, true); RNA_def_boolean(ot->srna, "ignore_background_click", @@ -5828,10 +6800,10 @@ enum { static void fake_neighbor_init(SculptSession *ss, const float max_dist) { const int totvert = SCULPT_vertex_count_get(ss); - ss->fake_neighbors.fake_neighbor_index = static_cast( + ss->fake_neighbors.fake_neighbor_index = static_cast( MEM_malloc_arrayN(totvert, sizeof(int), "fake neighbor")); for (int i = 0; i < totvert; i++) { - ss->fake_neighbors.fake_neighbor_index[i] = FAKE_NEIGHBOR_NONE; + ss->fake_neighbors.fake_neighbor_index[i].i = FAKE_NEIGHBOR_NONE; } ss->fake_neighbors.current_max_distance = max_dist; @@ -5842,9 +6814,9 @@ static void fake_neighbor_add(SculptSession *ss, PBVHVertRef v_a, PBVHVertRef v_ int v_index_a = BKE_pbvh_vertex_to_index(ss->pbvh, v_a); int v_index_b = BKE_pbvh_vertex_to_index(ss->pbvh, v_b); - if (ss->fake_neighbors.fake_neighbor_index[v_index_a] == FAKE_NEIGHBOR_NONE) { - ss->fake_neighbors.fake_neighbor_index[v_index_a] = v_index_b; - ss->fake_neighbors.fake_neighbor_index[v_index_b] = v_index_a; + if (ss->fake_neighbors.fake_neighbor_index[v_index_a].i == FAKE_NEIGHBOR_NONE) { + ss->fake_neighbors.fake_neighbor_index[v_index_a].i = v_index_b; + ss->fake_neighbors.fake_neighbor_index[v_index_b].i = v_index_a; } } @@ -5869,7 +6841,7 @@ static void do_fake_neighbor_search_task(SculptSession *ss, BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { int vd_topology_id = SCULPT_vertex_island_get(ss, vd.vertex); if (vd_topology_id != nvtd->current_topology_id && - ss->fake_neighbors.fake_neighbor_index[vd.index] == FAKE_NEIGHBOR_NONE) + ss->fake_neighbors.fake_neighbor_index[vd.index].i == FAKE_NEIGHBOR_NONE) { float distance_squared = len_squared_v3v3(vd.co, nearest_vertex_search_co); if (distance_squared < nvtd->nearest_vertex_distance_sq && @@ -5939,25 +6911,25 @@ struct SculptTopologyIDFloodFillData { void SCULPT_boundary_info_ensure(Object *object) { - using namespace blender; - SculptSession *ss = object->sculpt; - if (!ss->vertex_info.boundary.is_empty()) { - return; + blender::bke::sculpt::sculpt_vert_boundary_ensure(object); +} + +void SCULPT_ensure_vemap(SculptSession *ss) +{ + if (BKE_pbvh_type(ss->pbvh) != PBVH_BMESH && ss->vert_to_edge_map.is_empty()) { + ss->vert_to_edge_map = blender::bke::mesh::build_vert_to_edge_map( + ss->edges, ss->totvert, ss->vert_to_edge_offsets, ss->vert_to_edge_indices); } +} - Mesh *base_mesh = BKE_mesh_from_object(object); - - ss->vertex_info.boundary.resize(base_mesh->verts_num); - Array adjacent_faces_edge_count(base_mesh->edges_num, 0); - array_utils::count_indices(base_mesh->corner_edges(), adjacent_faces_edge_count); - - const blender::Span edges = base_mesh->edges(); - for (const int e : edges.index_range()) { - if (adjacent_faces_edge_count[e] < 2) { - const int2 &edge = edges[e]; - ss->vertex_info.boundary[edge[0]].set(); - ss->vertex_info.boundary[edge[1]].set(); - } +void SCULPT_ensure_epmap(SculptSession *ss) +{ + if (BKE_pbvh_type(ss->pbvh) != PBVH_BMESH && ss->edge_to_face_map.is_empty()) { + ss->edge_to_face_map = blender::bke::mesh::build_edge_to_face_map(ss->faces, + ss->corner_edges, + ss->totedges, + ss->edge_to_face_offsets, + ss->edge_to_face_indices); } } @@ -5983,7 +6955,7 @@ void SCULPT_fake_neighbors_ensure(Object *ob, const float max_dist) const PBVHVertRef from_v = BKE_pbvh_index_to_vertex(ss->pbvh, i); /* This vertex does not have a fake neighbor yet, search one for it. */ - if (ss->fake_neighbors.fake_neighbor_index[i] == FAKE_NEIGHBOR_NONE) { + if (ss->fake_neighbors.fake_neighbor_index[i].i == FAKE_NEIGHBOR_NONE) { const PBVHVertRef to_v = fake_neighbor_search(ob, from_v, max_dist); if (to_v.i != PBVH_REF_NONE) { /* Add the fake neighbor if available. */ @@ -6023,6 +6995,7 @@ NodeData node_begin(Object &object, const Cache *automasking, PBVHNode &node) } NodeData automask_data; + automask_data.ss = object.sculpt; automask_data.have_orig_data = automasking->settings.flags & (BRUSH_AUTOMASKING_BRUSH_NORMAL | BRUSH_AUTOMASKING_VIEW_NORMAL); @@ -6038,7 +7011,7 @@ NodeData node_begin(Object &object, const Cache *automasking, PBVHNode &node) void node_update(auto_mask::NodeData &automask_data, PBVHVertexIter &vd) { if (automask_data.have_orig_data) { - SCULPT_orig_vert_data_update(&automask_data.orig_data, &vd); + SCULPT_orig_vert_data_update(&automask_data.orig_data, vd.vertex); } } @@ -6072,6 +7045,8 @@ bool SCULPT_vertex_is_occluded(SculptSession *ss, PBVHVertRef vertex, bool origi srd.ray_normal = ray_normal; srd.depth = depth; srd.face_normal = face_normal; + srd.active_face.i = PBVH_REF_NONE; + srd.active_vertex.i = PBVH_REF_NONE; isect_ray_tri_watertight_v3_precalc(&srd.isect_precalc, ray_normal); bke::pbvh::raycast( @@ -6079,39 +7054,57 @@ bool SCULPT_vertex_is_occluded(SculptSession *ss, PBVHVertRef vertex, bool origi [&](PBVHNode &node, float *tmin) { sculpt_raycast_cb(node, srd, tmin); }, ray_start, ray_normal, - srd.original); + srd.original, + ss->stroke_id); return srd.hit; } void SCULPT_stroke_id_next(Object *ob) { - /* Manually wrap in int32 space to avoid tripping up undefined behavior - * sanitizers. - */ - ob->sculpt->stroke_id = uchar((int(ob->sculpt->stroke_id) + 1) & 255); + ushort *id = reinterpret_cast(&ob->sculpt->stroke_id); + + /* Try to avoid offending undefined behavior sanitizers. */ + id[0] = ushort((int(id[0]) + 1) % 65535); + id[1] = 0; } -void SCULPT_stroke_id_ensure(Object *ob) +int SCULPT_face_set_get(const SculptSession *ss, PBVHFaceRef face) { - using namespace blender; - SculptSession *ss = ob->sculpt; + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_BMESH: { + BMFace *f = reinterpret_cast(face.i); + return BM_ELEM_CD_GET_INT(f, ss->cd_faceset_offset); + } + case PBVH_FACES: + case PBVH_GRIDS: + return ss->face_sets[face.i]; + } - if (!ss->attrs.automasking_stroke_id) { - SculptAttributeParams params = {0}; - ss->attrs.automasking_stroke_id = BKE_sculpt_attribute_ensure( - ob, - bke::AttrDomain::Point, - CD_PROP_INT8, - SCULPT_ATTRIBUTE_NAME(automasking_stroke_id), - ¶ms); + BLI_assert_unreachable(); + + return 0; +} + +void SCULPT_face_set_set(SculptSession *ss, PBVHFaceRef face, int fset) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_BMESH: { + BMFace *f = reinterpret_cast(face.i); + BM_ELEM_CD_SET_INT(f, ss->cd_faceset_offset, fset); + break; + } + case PBVH_FACES: + case PBVH_GRIDS: + ss->face_sets[face.i] = fset; } } +namespace blender::ed::sculpt_paint { int SCULPT_vertex_island_get(const SculptSession *ss, PBVHVertRef vertex) { if (ss->attrs.topology_island_key) { - return *static_cast(SCULPT_vertex_attr_get(vertex, ss->attrs.topology_island_key)); + return vertex_attr_get(vertex, ss->attrs.topology_island_key); } return -1; @@ -6133,7 +7126,7 @@ void SCULPT_topology_islands_ensure(Object *ob) return; } - SculptAttributeParams params; + SculptAttributeParams params = {}; params.permanent = params.stroke_only = params.simple_array = false; ss->attrs.topology_island_key = BKE_sculpt_attribute_ensure( @@ -6164,8 +7157,7 @@ void SCULPT_topology_islands_ensure(Object *ob) PBVHVertRef vertex2 = stack.pop_last(); SculptVertexNeighborIter ni; - *static_cast( - SCULPT_vertex_attr_get(vertex2, ss->attrs.topology_island_key)) = island_nr; + vertex_attr_set(vertex2, ss->attrs.topology_island_key, island_nr); SCULPT_VERTEX_DUPLICATES_AND_NEIGHBORS_ITER_BEGIN (ss, vertex2, ni) { if (visit.add(ni.vertex) && hide::vert_any_face_visible_get(ss, ni.vertex)) { @@ -6180,6 +7172,7 @@ void SCULPT_topology_islands_ensure(Object *ob) ss->islands_valid = true; } +} // namespace blender::ed::sculpt_paint void SCULPT_cube_tip_init(Sculpt * /*sd*/, Object *ob, Brush *brush, float mat[4][4]) { diff --git a/source/blender/editors/sculpt_paint/sculpt_api.cc b/source/blender/editors/sculpt_paint/sculpt_api.cc new file mode 100644 index 00000000000..c2ba44a7612 --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_api.cc @@ -0,0 +1,555 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2006 by Nicholas Bishop. All rights reserved. */ + +/** \file + * \ingroup edsculpt + * Implements the Sculpt Mode tools. + */ + +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "BLI_alloca.h" +#include "BLI_array.h" +#include "BLI_bitmap.h" +#include "BLI_blenlib.h" +#include "BLI_dial_2d.h" +#include "BLI_ghash.h" +#include "BLI_gsqueue.h" +#include "BLI_hash.h" +#include "BLI_index_range.hh" +#include "BLI_link_utils.h" +#include "BLI_linklist.h" +#include "BLI_linklist_stack.h" +#include "BLI_listbase.h" +#include "BLI_math_color.h" +#include "BLI_math_color_blend.h" +#include "BLI_math_vector.h" +#include "BLI_memarena.h" +#include "BLI_offset_indices.hh" +#include "BLI_rand.h" +#include "BLI_set.hh" +#include "BLI_task.h" +#include "BLI_task.hh" +#include "BLI_timeit.hh" +#include "BLI_utildefines.h" +#include "BLI_vector.hh" +#include "BLI_time.h" + +#include "DNA_brush_types.h" +#include "DNA_customdata_types.h" +#include "DNA_listBase.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_node_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_view3d_types.h" + +#include "BKE_attribute.h" +#include "BKE_attribute.hh" +#include "BKE_brush.hh" +#include "BKE_ccg.h" +#include "BKE_colortools.hh" +#include "BKE_context.hh" +#include "BKE_idprop.h" +#include "BKE_image.h" +#include "BKE_key.hh" +#include "BKE_lib_id.hh" +#include "BKE_main.hh" +#include "BKE_mesh.h" +#include "BKE_mesh_fair.hh" +#include "BKE_mesh_mapping.hh" +#include "BKE_modifier.hh" +#include "BKE_multires.hh" +#include "BKE_node_runtime.hh" +#include "BKE_object.hh" +#include "BKE_paint.hh" +#include "BKE_particle.h" +#include "BKE_pbvh_api.hh" +#include "BKE_pointcache.h" +#include "BKE_report.hh" +#include "BKE_scene.hh" +#include "BKE_subdiv_ccg.hh" +#include "BKE_subsurf.hh" +#include "NOD_texture.h" + +#include "DEG_depsgraph.hh" +#include "DEG_depsgraph_query.hh" + +#include "IMB_colormanagement.hh" + +#include "GPU_batch.h" +#include "GPU_batch_presets.h" +#include "GPU_immediate.h" +#include "GPU_immediate_util.h" +#include "GPU_matrix.h" +#include "GPU_state.h" + +#include "WM_api.hh" +#include "WM_types.hh" + +#include "ED_paint.hh" +#include "ED_screen.hh" +#include "ED_sculpt.hh" +#include "ED_space_api.hh" +#include "ED_transform_snap_object_context.hh" +#include "ED_view3d.hh" + +#include "paint_intern.hh" +#include "sculpt_intern.hh" + +#include "RNA_access.hh" +#include "RNA_define.hh" + +#include "atomic_ops.h" + +#include "bmesh.hh" +#include "bmesh_log.hh" +#include "bmesh_tools.hh" + +#include "UI_resources.hh" + +#include +#include +#include + +using blender::float2; +using blender::float3; +using blender::IndexRange; +using blender::OffsetIndices; +using blender::Span; +using blender::MutableSpan; +using blender::Vector; +using blender::Set; + +using namespace blender::bke::paint; + +static bool sculpt_check_boundary_vertex_in_base_mesh(const SculptSession *ss, int index) +{ + BLI_assert(ss->vertex_info.boundary); + + return BLI_BITMAP_TEST(ss->vertex_info.boundary, index); +} + +eSculptBoundary SCULPT_edge_is_boundary(const SculptSession *ss, + const PBVHEdgeRef edge, + eSculptBoundary boundary_types) +{ + int oldflag = blender::bke::paint::edge_attr_get(edge, ss->attrs.edge_boundary_flags); + bool update = oldflag & (SCULPT_BOUNDARY_NEEDS_UPDATE | SCULPT_BOUNDARY_UPDATE_UV | + SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE); + + if (update) { + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_BMESH: { + BMEdge *e = reinterpret_cast(edge.i); + blender::bke::pbvh::update_edge_boundary_bmesh( + e, + ss->attrs.face_set ? ss->attrs.face_set->bmesh_cd_offset : -1, + ss->attrs.edge_boundary_flags->bmesh_cd_offset, + ss->attrs.flags->bmesh_cd_offset, + ss->attrs.valence->bmesh_cd_offset, + &ss->bm->ldata, + ss->sharp_angle_limit); + break; + } + case PBVH_FACES: { + Span nor = BKE_pbvh_get_vert_normals(ss->pbvh); + blender::bke::pbvh::update_edge_boundary_faces( + edge.i, + ss->vert_positions, + nor, + ss->edges, + ss->faces, + ss->poly_normals, + (int *)ss->attrs.edge_boundary_flags->data, + (int *)ss->attrs.boundary_flags->data, + ss->attrs.face_set ? (int *)ss->attrs.face_set->data : nullptr, + ss->sharp_edge, + ss->seam_edge, + ss->vert_to_face_map, + ss->edge_to_face_map, + ss->ldata, + ss->sharp_angle_limit, + ss->corner_verts, + ss->corner_edges); + break; + } + case PBVH_GRIDS: { + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + + blender::bke::pbvh::update_edge_boundary_grids( + edge.i, + ss->edges, + ss->faces, + (int *)ss->attrs.edge_boundary_flags->data, + (int *)ss->attrs.boundary_flags->data, + ss->attrs.face_set ? (int *)ss->attrs.face_set->data : nullptr, + ss->sharp_edge, + ss->seam_edge, + ss->vert_to_face_map, + ss->edge_to_face_map, + ss->ldata, + ss->subdiv_ccg, + key, + ss->sharp_angle_limit, + ss->corner_verts, + ss->corner_edges); + + break; + } + } + } + + return boundary_types & eSculptBoundary(blender::bke::paint::edge_attr_get( + edge, ss->attrs.edge_boundary_flags)); +} + +void SCULPT_edge_get_verts(const SculptSession *ss, + const PBVHEdgeRef edge, + PBVHVertRef *r_v1, + PBVHVertRef *r_v2) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_BMESH: { + BMEdge *e = (BMEdge *)edge.i; + r_v1->i = (intptr_t)e->v1; + r_v2->i = (intptr_t)e->v2; + break; + } + + case PBVH_FACES: { + r_v1->i = (intptr_t)ss->edges[edge.i][0]; + r_v2->i = (intptr_t)ss->edges[edge.i][1]; + break; + } + case PBVH_GRIDS: + // not supported yet + r_v1->i = r_v2->i = PBVH_REF_NONE; + break; + } +} + +PBVHVertRef SCULPT_edge_other_vertex(const SculptSession *ss, + const PBVHEdgeRef edge, + const PBVHVertRef vertex) +{ + PBVHVertRef v1, v2; + + SCULPT_edge_get_verts(ss, edge, &v1, &v2); + + return v1.i == vertex.i ? v2 : v1; +} + +static void grids_update_boundary_flags(const SculptSession *ss, PBVHVertRef vertex) +{ + blender::bke::pbvh::update_vert_boundary_grids(ss->pbvh, vertex.i, ss->face_sets); +} + +static void faces_update_boundary_flags(const SculptSession *ss, const PBVHVertRef vertex) +{ + blender::bke::pbvh::update_vert_boundary_faces((int *)ss->attrs.boundary_flags->data, + ss->face_sets, + ss->hide_poly, + ss->edges.data(), + ss->corner_verts.data(), + ss->corner_edges.data(), + ss->faces, + ss->vert_to_face_map, + vertex, + ss->sharp_edge, + ss->seam_edge, + static_cast(ss->attrs.flags->data), + static_cast(ss->attrs.valence->data)); + + /* We have to handle boundary here seperately. */ + + int *flag = vertex_attr_ptr(vertex, ss->attrs.boundary_flags); + *flag &= ~(SCULPT_CORNER_MESH | SCULPT_BOUNDARY_MESH); + + if (sculpt_check_boundary_vertex_in_base_mesh(ss, vertex.i)) { + *flag |= SCULPT_BOUNDARY_MESH; + + Span pmap = ss->vert_to_face_map[vertex.i]; + + if (pmap.size() < 4) { + bool ok = true; + + for (int poly : pmap) { + const IndexRange mp = ss->faces[poly]; + if (mp.size() < 4) { + ok = false; + } + } + if (ok) { + *flag |= SCULPT_CORNER_MESH; + } + else { + *flag &= ~SCULPT_CORNER_MESH; + } + } + } +} + +static bool sculpt_vertex_ensure_boundary(const SculptSession *ss, + const PBVHVertRef vertex, + int mask) +{ + eSculptBoundary flag = *vertex_attr_ptr(vertex, ss->attrs.boundary_flags); + bool needs_update = flag & (SCULPT_BOUNDARY_NEEDS_UPDATE | SCULPT_BOUNDARY_UPDATE_UV); + + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_BMESH: { + BMVert *v = reinterpret_cast(vertex.i); + + if (needs_update) { + blender::bke::pbvh::update_vert_boundary_bmesh(ss->cd_faceset_offset, + ss->cd_vert_node_offset, + ss->cd_face_node_offset, + ss->cd_vcol_offset, + ss->attrs.boundary_flags->bmesh_cd_offset, + ss->attrs.flags->bmesh_cd_offset, + ss->attrs.valence->bmesh_cd_offset, + (BMVert *)vertex.i, + &ss->bm->ldata, + ss->sharp_angle_limit); + } + else if ((mask & (SCULPT_BOUNDARY_SHARP_ANGLE | SCULPT_CORNER_SHARP_ANGLE)) && + flag & SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE) + { + blender::bke::pbvh::update_sharp_vertex_bmesh( + v, ss->attrs.boundary_flags->bmesh_cd_offset, ss->sharp_angle_limit); + } + + break; + } + case PBVH_FACES: { + if (needs_update) { + faces_update_boundary_flags(ss, vertex); + } + break; + } + + case PBVH_GRIDS: { + if (needs_update) { + grids_update_boundary_flags(ss, vertex); + needs_update = false; + } + break; + } + } + + return needs_update; +} + +eSculptCorner SCULPT_vertex_is_corner(const SculptSession *ss, + const PBVHVertRef vertex, + eSculptCorner cornertype) +{ + sculpt_vertex_ensure_boundary(ss, vertex, int(cornertype)); + eSculptCorner flag = eSculptCorner(vertex_attr_get(vertex, ss->attrs.boundary_flags)); + + return flag & cornertype; +} + +eSculptBoundary SCULPT_vertex_is_boundary(const SculptSession *ss, + const PBVHVertRef vertex, + eSculptBoundary boundary_types) +{ + eSculptBoundary flag = vertex_attr_get(vertex, ss->attrs.boundary_flags); + + sculpt_vertex_ensure_boundary(ss, vertex, int(boundary_types)); + + if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS && boundary_types & SCULPT_BOUNDARY_MESH) { + /* TODO: BKE_pbvh_update_vert_boundary_grids does not yet support mesh boundaries for + * PBVH_GRIDS.*/ + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; + SubdivCCGCoord coord{}; + coord.grid_index = grid_index; + coord.x = vertex_index % key->grid_size; + coord.y = vertex_index / key->grid_size; + int v1, v2; + const SubdivCCGAdjacencyType adjacency = BKE_subdiv_ccg_coarse_mesh_adjacency_info_get( + *ss->subdiv_ccg, coord, ss->corner_verts, ss->faces, v1, v2); + + switch (adjacency) { + case SUBDIV_CCG_ADJACENT_VERTEX: + flag |= sculpt_check_boundary_vertex_in_base_mesh(ss, v1) ? SCULPT_BOUNDARY_MESH : + (eSculptBoundary)0; + case SUBDIV_CCG_ADJACENT_EDGE: + if (sculpt_check_boundary_vertex_in_base_mesh(ss, v1) && + sculpt_check_boundary_vertex_in_base_mesh(ss, v2)) + { + flag |= SCULPT_BOUNDARY_MESH; + } + case SUBDIV_CCG_ADJACENT_NONE: + break; + } + } + + return flag & boundary_types; +} + +bool SCULPT_vertex_check_origdata(SculptSession *ss, PBVHVertRef vertex) +{ + return blender::bke::paint::get_original_vertex(ss, vertex, nullptr, nullptr, nullptr, nullptr); +} + +static int sculpt_calc_valence(const struct SculptSession *ss, PBVHVertRef vertex) +{ + + int tot = 0; + + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_BMESH: { + BMVert *v = reinterpret_cast(vertex.i); + tot = BM_vert_edge_count(v); + break; + } + case PBVH_FACES: { + Vector edges; + for (int edge : ss->vert_to_face_map[vertex.i]) { + if (!edges.contains(edge)) { + edges.append(edge); + } + } + + tot = edges.size(); + break; + } + case PBVH_GRIDS: { + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; + + SubdivCCGCoord coord{}; + coord.grid_index = grid_index; + coord.x = vertex_index % key->grid_size; + coord.y = vertex_index / key->grid_size; + + SubdivCCGNeighbors neighbors; + BKE_subdiv_ccg_neighbor_coords_get(*ss->subdiv_ccg, coord, true, neighbors); + + tot = neighbors.size; + break; + } + } + + return tot; +} + +int SCULPT_vertex_valence_get(const struct SculptSession *ss, PBVHVertRef vertex) +{ + uint8_t flag = vertex_attr_get(vertex, ss->attrs.flags); + + if (flag & SCULPTFLAG_NEED_VALENCE) { + int tot = sculpt_calc_valence(ss, vertex); + + vertex_attr_set(vertex, ss->attrs.valence, tot); + *vertex_attr_ptr(vertex, ss->attrs.flags) &= ~SCULPTFLAG_NEED_VALENCE; + + return tot; + } + +#if 0 + int tot = vertex_attr_get(vertex, ss->attrs.valence); + int real_tot = sculpt_calc_valence(ss, vertex); + + if (tot != real_tot) { + printf("%s: invalid valence! got %d expected %d\n", tot, real_tot); + } +#endif + + return vertex_attr_get(vertex, ss->attrs.valence); +} + +/* See SCULPT_stroke_id_test. */ +void SCULPT_stroke_id_ensure(Object *ob) +{ + BKE_sculpt_ensure_sculpt_layers(ob); +} + +int SCULPT_get_tool(const SculptSession *ss, const Brush *br) +{ + if (ss->cache && ss->cache->tool_override) { + return ss->cache->tool_override; + } + + return br->sculpt_tool; +} + +void SCULPT_ensure_persistent_layers(SculptSession *ss, Object *ob) +{ + SculptAttributeParams params = {}; + params.permanent = true; + + if (!ss->attrs.persistent_co) { + ss->attrs.persistent_co = BKE_sculpt_attribute_ensure( + ob, AttrDomain::Point, CD_PROP_FLOAT3, SCULPT_ATTRIBUTE_NAME(persistent_co), ¶ms); + ss->attrs.persistent_no = BKE_sculpt_attribute_ensure( + ob, AttrDomain::Point, CD_PROP_FLOAT3, SCULPT_ATTRIBUTE_NAME(persistent_no), ¶ms); + ss->attrs.persistent_disp = BKE_sculpt_attribute_ensure( + ob, AttrDomain::Point, CD_PROP_FLOAT, SCULPT_ATTRIBUTE_NAME(persistent_disp), ¶ms); + } +} + +namespace blender::ed::sculpt_paint::dyntopo { +void apply_settings(Scene * /*scene*/, SculptSession *ss, Sculpt *sculpt, Brush *brush) +{ + using namespace blender::bke::dyntopo; + + if (!brush) { + ss->cached_dyntopo = sculpt->dyntopo; + return; + } + + DynTopoSettings *ds1 = &brush->dyntopo; + DynTopoSettings *ds2 = &sculpt->dyntopo; + + DynTopoSettings *ds_final = &ss->cached_dyntopo; + + ds_final->inherit = BKE_brush_dyntopo_inherit_flags(brush); + ds_final->flag = 0; + + for (int i = 0; i < DYNTOPO_MAX_FLAGS; i++) { + if (ds_final->inherit & (1 << i)) { + ds_final->flag |= ds2->flag & (1 << i); + } + else { + ds_final->flag |= ds1->flag & (1 << i); + } + } + + ds_final->constant_detail = ds_final->inherit & DYNTOPO_INHERIT_CONSTANT_DETAIL ? + ds2->constant_detail : + ds1->constant_detail; + ds_final->detail_percent = ds_final->inherit & DYNTOPO_INHERIT_DETAIL_PERCENT ? + ds2->detail_percent : + ds1->detail_percent; + ds_final->detail_size = ds_final->inherit & DYNTOPO_INHERIT_DETAIL_SIZE ? ds2->detail_size : + ds1->detail_size; + ds_final->mode = ds_final->inherit & DYNTOPO_INHERIT_MODE ? ds2->mode : ds1->mode; + ds_final->radius_scale = ds_final->inherit & DYNTOPO_INHERIT_RADIUS_SCALE ? ds2->radius_scale : + ds1->radius_scale; + ds_final->spacing = ds_final->inherit & DYNTOPO_INHERIT_SPACING ? ds2->spacing : ds1->spacing; + ds_final->repeat = ds_final->inherit & DYNTOPO_INHERIT_REPEAT ? ds2->repeat : ds1->repeat; + ds_final->quality = ds_final->inherit & DYNTOPO_INHERIT_QUALITY ? ds2->quality : ds1->quality; +} +} // namespace blender::ed::sculpt_paint::dyntopo + +bool SCULPT_face_is_hidden(const SculptSession *ss, PBVHFaceRef face) +{ + if (ss->bm) { + BMFace *f = reinterpret_cast(face.i); + return BM_elem_flag_test(f, BM_ELEM_HIDDEN); + } + else { + return ss->hide_poly ? ss->hide_poly[face.i] : false; + } +} diff --git a/source/blender/editors/sculpt_paint/sculpt_automasking.cc b/source/blender/editors/sculpt_paint/sculpt_automasking.cc index 78b2598c8cc..f6620cfee43 100644 --- a/source/blender/editors/sculpt_paint/sculpt_automasking.cc +++ b/source/blender/editors/sculpt_paint/sculpt_automasking.cc @@ -30,6 +30,12 @@ #include #include +using blender::float3; +using blender::IndexRange; +using blender::Set; +using blender::Vector; +using namespace blender::bke::paint; + namespace blender::ed::sculpt_paint::auto_mask { Cache *active_cache_get(SculptSession *ss) @@ -56,7 +62,7 @@ bool mode_enabled(const Sculpt *sd, const Brush *br, const eAutomasking_flag mod bool is_enabled(const Sculpt *sd, const SculptSession *ss, const Brush *br) { - if (ss && br && dyntopo::stroke_is_dyntopo(ss, br)) { + if (ss && br && dyntopo::stroke_is_dyntopo(ss, sd, br)) { return false; } if (mode_enabled(sd, br, BRUSH_AUTOMASKING_TOPOLOGY)) { @@ -246,13 +252,14 @@ static float calc_view_normal_factor(Cache &automasking, static float calc_view_occlusion_factor(Cache &automasking, SculptSession *ss, PBVHVertRef vertex, - uchar stroke_id, - const NodeData & /*automask_data*/) -{ - char f = *(char *)SCULPT_vertex_attr_get(vertex, ss->attrs.automasking_occlusion); + const NodeData & /*automask_data*/, - if (stroke_id != automasking.current_stroke_id) { - f = *(char *)SCULPT_vertex_attr_get( + bool force = false) +{ + char f = vertex_attr_get(vertex, ss->attrs.automasking_occlusion); + + if (force || blender::bke::sculpt::stroke_id_test(ss, vertex, STROKEID_USER_OCCLUSION)) { + f = *vertex_attr_ptr( vertex, ss->attrs.automasking_occlusion) = SCULPT_vertex_is_occluded(ss, vertex, true) ? 2 : 1; } @@ -266,9 +273,8 @@ static float automasking_factor_end(SculptSession *ss, PBVHVertRef vertex, float value) { - if (ss->attrs.automasking_stroke_id) { - *(uchar *)SCULPT_vertex_attr_get( - vertex, ss->attrs.automasking_stroke_id) = automasking->current_stroke_id; + if (ss->attrs.stroke_id) { + blender::bke::sculpt::stroke_id_test(ss, vertex, STROKEID_USER_AUTOMASKING); } return value; @@ -428,7 +434,7 @@ static void calc_blurred_cavity(SculptSession *ss, factor_sum = calc_cavity_factor(automasking, factor_sum); - *(float *)SCULPT_vertex_attr_get(vertex, ss->attrs.automasking_cavity) = factor_sum; + vertex_attr_set(vertex, ss->attrs.automasking_cavity, factor_sum); } int settings_hash(const Object &ob, const Cache &automasking) @@ -481,13 +487,11 @@ int settings_hash(const Object &ob, const Cache &automasking) static float calc_cavity_factor(Cache *automasking, SculptSession *ss, PBVHVertRef vertex) { - uchar stroke_id = *(uchar *)SCULPT_vertex_attr_get(vertex, ss->attrs.automasking_stroke_id); - - if (stroke_id != automasking->current_stroke_id) { + if (blender::bke::sculpt::stroke_id_test_no_update(ss, vertex, STROKEID_USER_AUTOMASKING)) { calc_blurred_cavity(ss, automasking, automasking->settings.cavity_blur_steps, vertex); } - float factor = *(float *)SCULPT_vertex_attr_get(vertex, ss->attrs.automasking_cavity); + float factor = vertex_attr_get(vertex, ss->attrs.automasking_cavity); bool inverted = automasking->settings.flags & BRUSH_AUTOMASKING_CAVITY_INVERTED; if ((automasking->settings.flags & BRUSH_AUTOMASKING_CAVITY_ALL) && @@ -524,7 +528,7 @@ float factor_get(Cache *automasking, * automasking information can't be computed in real time per vertex and needs to be * initialized for the whole mesh when the stroke starts. */ if (ss->attrs.automasking_factor) { - float factor = *(float *)SCULPT_vertex_attr_get(vert, ss->attrs.automasking_factor); + float factor = vertex_attr_get(vert, ss->attrs.automasking_factor); if (automasking->settings.flags & BRUSH_AUTOMASKING_CAVITY_ALL) { factor *= calc_cavity_factor(automasking, ss, vert); @@ -533,16 +537,10 @@ float factor_get(Cache *automasking, return automasking_factor_end(ss, automasking, vert, factor * mask); } - uchar stroke_id = ss->attrs.automasking_stroke_id ? - *(uchar *)SCULPT_vertex_attr_get(vert, ss->attrs.automasking_stroke_id) : - -1; - bool do_occlusion = (automasking->settings.flags & (BRUSH_AUTOMASKING_VIEW_OCCLUSION | BRUSH_AUTOMASKING_VIEW_NORMAL)) == (BRUSH_AUTOMASKING_VIEW_OCCLUSION | BRUSH_AUTOMASKING_VIEW_NORMAL); - if (do_occlusion && - calc_view_occlusion_factor(*automasking, ss, vert, stroke_id, *automask_data)) - { + if (do_occlusion && calc_view_occlusion_factor(*automasking, ss, vert, *automask_data)) { return automasking_factor_end(ss, automasking, vert, 0.0f); } @@ -560,7 +558,7 @@ float factor_get(Cache *automasking, } if (automasking->settings.flags & BRUSH_AUTOMASKING_BOUNDARY_EDGES) { - if (SCULPT_vertex_is_boundary(ss, vert)) { + if (SCULPT_vertex_is_boundary(ss, vert, SCULPT_BOUNDARY_MESH)) { return 0.0f; } } @@ -605,8 +603,9 @@ static bool floodfill_cb( { AutomaskFloodFillData *data = (AutomaskFloodFillData *)userdata; - *(float *)SCULPT_vertex_attr_get(to_v, ss->attrs.automasking_factor) = 1.0f; - *(float *)SCULPT_vertex_attr_get(from_v, ss->attrs.automasking_factor) = 1.0f; + vertex_attr_set(to_v, ss->attrs.automasking_factor, 1.0f); + vertex_attr_set(from_v, ss->attrs.automasking_factor, 1.0f); + return (!data->use_radius || SCULPT_is_vertex_inside_brush_radius_symm( SCULPT_vertex_co_get(ss, to_v), data->location, data->radius, data->symm)); @@ -621,7 +620,7 @@ static void topology_automasking_init(const Sculpt *sd, Object *ob) for (int i : IndexRange(totvert)) { PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); - (*(float *)SCULPT_vertex_attr_get(vertex, ss->attrs.automasking_factor)) = 0.0f; + vertex_attr_set(vertex, ss->attrs.automasking_factor, 0.0f); } /* Flood fill automask to connected vertices. Limited to vertices inside @@ -656,7 +655,7 @@ static void init_face_sets_masking(const Sculpt *sd, Object *ob) PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); if (!face_set::vert_has_face_set(ss, vertex, active_face_set)) { - *(float *)SCULPT_vertex_attr_get(vertex, ss->attrs.automasking_factor) = 0.0f; + vertex_attr_set(vertex, ss->attrs.automasking_factor, 0.0f); } } } @@ -676,7 +675,7 @@ static void init_boundary_masking(Object *ob, eBoundaryAutomaskMode mode, int pr edge_distance[i] = EDGE_DISTANCE_INF; switch (mode) { case AUTOMASK_INIT_BOUNDARY_EDGES: - if (SCULPT_vertex_is_boundary(ss, vertex)) { + if (SCULPT_vertex_is_boundary(ss, vertex, SCULPT_BOUNDARY_MESH)) { edge_distance[i] = 0; } break; @@ -714,8 +713,8 @@ static void init_boundary_masking(Object *ob, eBoundaryAutomaskMode mode, int pr const float p = 1.0f - (float(edge_distance[i]) / float(propagation_steps)); const float edge_boundary_automask = pow2f(p); - *(float *)SCULPT_vertex_attr_get( - vertex, ss->attrs.automasking_factor) *= (1.0f - edge_boundary_automask); + *vertex_attr_ptr(vertex, + ss->attrs.automasking_factor) *= (1.0f - edge_boundary_automask); } } @@ -772,21 +771,21 @@ static void normal_occlusion_automasking_fill(Cache &automasking, for (int i = 0; i < totvert; i++) { PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); - float f = *(float *)SCULPT_vertex_attr_get(vertex, ss->attrs.automasking_factor); + float f = vertex_attr_get(vertex, ss->attrs.automasking_factor); if (int(mode) & BRUSH_AUTOMASKING_VIEW_NORMAL) { if (int(mode) & BRUSH_AUTOMASKING_VIEW_OCCLUSION) { - f *= calc_view_occlusion_factor(automasking, ss, vertex, -1, nodedata); + f *= calc_view_occlusion_factor(automasking, ss, vertex, nodedata, true); } f *= calc_view_normal_factor(automasking, ss, vertex, nodedata); } - if (ss->attrs.automasking_stroke_id) { - *(uchar *)SCULPT_vertex_attr_get(vertex, ss->attrs.automasking_stroke_id) = ss->stroke_id; + if (ss->attrs.stroke_id) { + blender::bke::sculpt::stroke_id_test(ss, vertex, STROKEID_USER_AUTOMASKING); } - *(float *)SCULPT_vertex_attr_get(vertex, ss->attrs.automasking_factor) = f; + vertex_attr_set(vertex, ss->attrs.automasking_factor, f); } } @@ -909,7 +908,7 @@ std::unique_ptr cache_init(const Sculpt *sd, const Brush *brush, Object * for (int i : IndexRange(totvert)) { PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); - (*(float *)SCULPT_vertex_attr_get(vertex, ss->attrs.automasking_factor)) = initial_value; + vertex_attr_set(vertex, ss->attrs.automasking_factor, initial_value); } /* Additive modes. */ @@ -920,8 +919,10 @@ std::unique_ptr cache_init(const Sculpt *sd, const Brush *brush, Object * topology_automasking_init(sd, ob); } - if (mode_enabled(sd, brush, BRUSH_AUTOMASKING_FACE_SETS)) { - SCULPT_vertex_random_access_ensure(ss); + /* Draw face sets brush handles face set automasking on its own. */ + if (mode_enabled(sd, brush, BRUSH_AUTOMASKING_FACE_SETS) && + !(brush && brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS && !ss->cache->alt_smooth)) + { init_face_sets_masking(sd, ob); } @@ -946,4 +947,11 @@ std::unique_ptr cache_init(const Sculpt *sd, const Brush *brush, Object * return automasking; } +bool needs_original(Sculpt *sd, Brush *brush) +{ + return calc_effective_bits(sd, brush) & + (BRUSH_AUTOMASKING_CAVITY_ALL | BRUSH_AUTOMASKING_BRUSH_NORMAL | + BRUSH_AUTOMASKING_VIEW_NORMAL); +} + } // namespace blender::ed::sculpt_paint::auto_mask diff --git a/source/blender/editors/sculpt_paint/sculpt_boundary.cc b/source/blender/editors/sculpt_paint/sculpt_boundary.cc index 161f8cc43f3..2752c820de0 100644 --- a/source/blender/editors/sculpt_paint/sculpt_boundary.cc +++ b/source/blender/editors/sculpt_paint/sculpt_boundary.cc @@ -8,14 +8,22 @@ #include "MEM_guardedalloc.h" +#include "BLI_alloca.h" +#include "BLI_array.h" +#include "BLI_blenlib.h" +#include "BLI_math_vector.h" #include "BLI_task.h" #include "DNA_brush_types.h" +#include "DNA_meshdata_types.h" #include "DNA_object_types.h" #include "BKE_brush.hh" #include "BKE_ccg.h" #include "BKE_colortools.hh" +#include "BKE_context.hh" +#include "BKE_global.hh" +#include "BKE_object.hh" #include "BKE_paint.hh" #include "BKE_pbvh_api.hh" @@ -25,14 +33,42 @@ #include "GPU_immediate.h" #include "GPU_state.h" -#include -#include +#include "bmesh.hh" + +#include "ED_mesh.hh" +#include "ED_object.hh" + +#include "DEG_depsgraph.hh" +#include "DEG_depsgraph_build.hh" +#include "WM_api.hh" +#include "WM_types.hh" + +#include +#include + +#if 1 +# ifdef NDEBUG +# define NDEBUG_UNDEFD +# undef NDEBUG +# endif + +# include "BLI_assert.h" + +# ifdef NDEBUG_UNDEFD +# define NDEBUG 1 +# endif +#endif #define BOUNDARY_VERTEX_NONE -1 #define BOUNDARY_STEPS_NONE -1 +#define TSTN 4 + namespace blender::ed::sculpt_paint::boundary { +static void boundary_color_vis(SculptSession *ss, SculptBoundary *boundary); +static void SCULPT_boundary_build_smoothco(SculptSession *ss, SculptBoundary *boundary); + struct BoundaryInitialVertexFloodFillData { PBVHVertRef initial_vertex; int initial_vertex_i; @@ -63,7 +99,7 @@ static bool boundary_initial_vertex_floodfill_cb( data->floodfill_steps[to_v_i] = data->floodfill_steps[from_v_i]; } - if (SCULPT_vertex_is_boundary(ss, to_v)) { + if (SCULPT_vertex_is_boundary(ss, to_v, SCULPT_BOUNDARY_MESH)) { if (data->floodfill_steps[to_v_i] < data->boundary_initial_vertex_steps) { data->boundary_initial_vertex_steps = data->floodfill_steps[to_v_i]; data->boundary_initial_vertex_i = to_v_i; @@ -82,8 +118,7 @@ static PBVHVertRef sculpt_boundary_get_closest_boundary_vertex(SculptSession *ss const PBVHVertRef initial_vertex, const float radius) { - - if (SCULPT_vertex_is_boundary(ss, initial_vertex)) { + if (SCULPT_vertex_is_boundary(ss, initial_vertex, SCULPT_BOUNDARY_MESH)) { return initial_vertex; } @@ -97,7 +132,7 @@ static PBVHVertRef sculpt_boundary_get_closest_boundary_vertex(SculptSession *ss fdata.boundary_initial_vertex_steps = INT_MAX; fdata.radius_sq = radius * radius; - fdata.floodfill_steps = MEM_cnew_array(SCULPT_vertex_count_get(ss), __func__); + fdata.floodfill_steps = MEM_cnew_array(SCULPT_vertex_count_get(ss) * TSTN, __func__); flood_fill::execute(ss, &flood, boundary_initial_vertex_floodfill_cb, &fdata); @@ -144,10 +179,10 @@ static void sculpt_boundary_preview_edge_add(SculptBoundary *boundary, if (boundary->edges_num >= boundary->edges_capacity) { boundary->edges_capacity += BOUNDARY_INDICES_BLOCK_SIZE; - boundary->edges = static_cast( - MEM_reallocN_id(boundary->edges, - boundary->edges_capacity * sizeof(SculptBoundaryPreviewEdge), - "boundary edges")); + boundary->edges = (SculptBoundaryPreviewEdge *)MEM_reallocN_id( + boundary->edges, + boundary->edges_capacity * sizeof(SculptBoundaryPreviewEdge) * TSTN, + "boundary edges"); } }; @@ -169,7 +204,7 @@ static bool sculpt_boundary_is_vertex_in_editable_boundary(SculptSession *ss, SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, initial_vertex, ni) { if (hide::vert_visible_get(ss, ni.vertex)) { neighbor_count++; - if (SCULPT_vertex_is_boundary(ss, ni.vertex)) { + if (SCULPT_vertex_is_boundary(ss, ni.vertex, SCULPT_BOUNDARY_MESH)) { boundary_vertex_count++; } } @@ -209,7 +244,8 @@ static bool boundary_floodfill_cb( BoundaryFloodFillData *data = static_cast(userdata); SculptBoundary *boundary = data->boundary; - if (!SCULPT_vertex_is_boundary(ss, to_v)) { + + if (!SCULPT_vertex_is_boundary(ss, to_v, SCULPT_BOUNDARY_MESH)) { return false; } const float edge_len = len_v3v3(SCULPT_vertex_co_get(ss, from_v), @@ -225,10 +261,75 @@ static bool boundary_floodfill_cb( return sculpt_boundary_is_vertex_in_editable_boundary(ss, to_v); } -static void sculpt_boundary_indices_init(SculptSession *ss, +static float *calc_boundary_tangent(SculptSession *ss, SculptBoundary *boundary) +{ + const int totvert = SCULPT_vertex_count_get(ss); + float dir[3]; + + float(*tangents)[3] = MEM_cnew_array(totvert, "boundary->boundary_tangents"); + + for (int i = 0; i < totvert; i++) { + float f1 = boundary->boundary_dist[i]; + + if (f1 == FLT_MAX) { + continue; + } + + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + const float *co1 = SCULPT_vertex_co_get(ss, vertex); + + zero_v3(dir); + + SculptVertexNeighborIter ni; + + float no1[3]; + SCULPT_vertex_normal_get(ss, vertex, no1); + + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { + const float *co2 = SCULPT_vertex_co_get(ss, ni.vertex); + float no2[3]; + + SCULPT_vertex_normal_get(ss, ni.vertex, no2); + + // int i2 = BKE_pbvh_vertex_to_index(ss->pbvh, ni.vertex); + int i2 = ni.index; + + float f2 = boundary->boundary_dist[i2]; + float dir2[3]; + + sub_v3_v3v3(dir2, co2, co1); + + if (f2 == FLT_MAX) { + continue; + } + + float distsqr = len_squared_v3v3(co1, co2); + if (distsqr == 0.0f) { + continue; + } + + float w = (f2 - f1) / distsqr; + + mul_v3_fl(dir2, w); + add_v3_v3(dir, dir2); + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + + normalize_v3(dir); + negate_v3(dir); + + copy_v3_v3(tangents[i], dir); + } + + return (float *)tangents; +} + +static void sculpt_boundary_indices_init(Object *ob, + SculptSession *ss, SculptBoundary *boundary, const bool init_boundary_distances, - const PBVHVertRef initial_boundary_vertex) + const PBVHVertRef initial_boundary_vertex, + float radius) { const int totvert = SCULPT_vertex_count_get(ss); @@ -236,10 +337,11 @@ static void sculpt_boundary_indices_init(SculptSession *ss, MEM_malloc_arrayN(BOUNDARY_INDICES_BLOCK_SIZE, sizeof(PBVHVertRef), __func__)); if (init_boundary_distances) { - boundary->distance = static_cast(MEM_calloc_arrayN(totvert, sizeof(float), __func__)); + boundary->distance = (float *)MEM_calloc_arrayN( + totvert, sizeof(float) * TSTN, "boundary distances"); } - boundary->edges = static_cast( - MEM_malloc_arrayN(BOUNDARY_INDICES_BLOCK_SIZE, sizeof(SculptBoundaryPreviewEdge), __func__)); + boundary->edges = (SculptBoundaryPreviewEdge *)MEM_malloc_arrayN( + BOUNDARY_INDICES_BLOCK_SIZE, sizeof(SculptBoundaryPreviewEdge) * TSTN, "boundary edges"); GSet *included_verts = BLI_gset_int_new_ex("included verts", BOUNDARY_INDICES_BLOCK_SIZE); SculptFloodFill flood; @@ -263,6 +365,67 @@ static void sculpt_boundary_indices_init(SculptSession *ss, flood_fill::execute(ss, &flood, boundary_floodfill_cb, &fdata); + GSet *boundary_verts; + + boundary_verts = included_verts; + + boundary->boundary_closest = MEM_cnew_array(totvert, "boundary_closest"); + boundary->boundary_dist = geodesic::distances_create( + ob, boundary_verts, radius, boundary->boundary_closest, {}); + + boundary->boundary_tangents = (float(*)[3])calc_boundary_tangent(ss, boundary); + +#if 1 // smooth geodesic tangent field + float(*boundary_tangents)[3] = MEM_cnew_array(totvert, "boundary_tangents"); + + for (int iteration = 0; iteration < 4; iteration++) { + for (int i = 0; i < totvert; i++) { + + if (boundary->boundary_dist[i] == FLT_MAX) { + copy_v3_v3(boundary_tangents[i], boundary->boundary_tangents[i]); + continue; + } + + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + float tot = 0.0f; + + float tan[3] = {0.0f, 0.0f, 0.0f}; + + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { + if (boundary->boundary_dist[ni.index] == FLT_MAX) { + continue; + } + + add_v3_v3(tan, boundary->boundary_tangents[ni.index]); + + tot += 1.0f; + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + + if (tot == 0.0f) { + continue; + } + + normalize_v3(tan); + interp_v3_v3v3(boundary_tangents[i], boundary->boundary_tangents[i], tan, 0.75f); + normalize_v3(boundary_tangents[i]); + } + + float(*tmp)[3] = boundary_tangents; + boundary_tangents = boundary->boundary_tangents; + boundary->boundary_tangents = tmp; + } + + MEM_SAFE_FREE(boundary_tangents); +#endif + + boundary_color_vis(ss, boundary); + + if (boundary_verts != included_verts) { + BLI_gset_free(boundary_verts, nullptr); + } + /* Check if the boundary loops into itself and add the extra preview edge to close the loop. */ if (fdata.last_visited_vertex.i != BOUNDARY_VERTEX_NONE && sculpt_boundary_is_vertex_in_editable_boundary(ss, fdata.last_visited_vertex)) @@ -282,6 +445,55 @@ static void sculpt_boundary_indices_init(SculptSession *ss, BLI_gset_free(included_verts, nullptr); } +static void boundary_color_vis(SculptSession *ss, SculptBoundary *boundary) +{ + if (boundary->boundary_dist && G.debug_value == 890 && ss->bm && + CustomData_has_layer(&ss->bm->vdata, CD_PROP_COLOR)) + { + const int cd_color = CustomData_get_offset(&ss->bm->vdata, CD_PROP_COLOR); + BM_mesh_elem_index_ensure(ss->bm, BM_VERT); + + BMIter iter; + BMVert *v; + int i = 0; + + float min = 1e17f, max = -1e17f; + + // calc bounds + BM_ITER_MESH_INDEX (v, &iter, ss->bm, BM_VERTS_OF_MESH, i) { + float f = boundary->boundary_dist[i]; + + if (f == FLT_MAX) { + continue; + } + + min = std::min(min, f); + max = std::max(max, f); + } + + float scale = max != min ? 1.0f / (max - min) : 0.0f; + + BM_ITER_MESH_INDEX (v, &iter, ss->bm, BM_VERTS_OF_MESH, i) { + MPropCol *mcol = BM_ELEM_CD_PTR(v, cd_color); + + float f = boundary->boundary_dist[i]; + + if (f == FLT_MAX) { + mcol->color[0] = mcol->color[1] = 1.0f; + mcol->color[2] = 0.0f; + mcol->color[3] = 1.0f; + continue; + } + else { + f = (f - min) * scale; + } + + mcol->color[0] = mcol->color[1] = mcol->color[2] = f; + mcol->color[3] = 1.0f; + } + } +} + /** * This functions initializes all data needed to calculate falloffs and deformation from the * boundary into the mesh into a #SculptBoundaryEditInfo array. This includes how many steps are @@ -297,8 +509,8 @@ static void sculpt_boundary_edit_data_init(SculptSession *ss, const bool has_duplicates = BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS; - boundary->edit_info = static_cast( - MEM_malloc_arrayN(totvert, sizeof(SculptBoundaryEditInfo), __func__)); + boundary->edit_info = (SculptBoundaryEditInfo *)MEM_malloc_arrayN( + totvert, sizeof(SculptBoundaryEditInfo) * TSTN, "Boundary edit info"); for (int i = 0; i < totvert; i++) { boundary->edit_info[i].original_vertex_i = BOUNDARY_VERTEX_NONE; @@ -356,6 +568,7 @@ static void sculpt_boundary_edit_data_init(SculptSession *ss, { continue; } + boundary->edit_info[ni.index].original_vertex_i = boundary->edit_info[from_v_i].original_vertex_i; @@ -416,14 +629,14 @@ static void sculpt_boundary_edit_data_init(SculptSession *ss, /* This functions assigns a falloff factor to each one of the SculptBoundaryEditInfo structs based * on the brush curve and its propagation steps. The falloff goes from the boundary into the mesh. */ -static void sculpt_boundary_falloff_factor_init(SculptSession *ss, - SculptBoundary *boundary, - Brush *brush, - const float radius) +static void sculpt_boundary_falloff_factor_init( + SculptSession *ss, Sculpt * /*sd*/, SculptBoundary *boundary, Brush *brush, const float radius) { const int totvert = SCULPT_vertex_count_get(ss); BKE_curvemapping_init(brush->curve); + int boundary_type = brush->boundary_falloff_type; + for (int i = 0; i < totvert; i++) { if (boundary->edit_info[i].propagation_steps_num != -1) { boundary->edit_info[i].strength_factor = BKE_brush_curve_strength( @@ -448,18 +661,18 @@ static void sculpt_boundary_falloff_factor_init(SculptSession *ss, float falloff_distance = 0.0f; float direction = 1.0f; - switch (brush->boundary_falloff_type) { + switch (boundary_type) { case BRUSH_BOUNDARY_FALLOFF_RADIUS: falloff_distance = boundary_distance; break; case BRUSH_BOUNDARY_FALLOFF_LOOP: { - const int div = boundary_distance / radius; + const int div = (int)(boundary_distance / radius); const float mod = fmodf(boundary_distance, radius); falloff_distance = div % 2 == 0 ? mod : radius - mod; break; } case BRUSH_BOUNDARY_FALLOFF_LOOP_INVERT: { - const int div = boundary_distance / radius; + const int div = (int)(boundary_distance / radius); const float mod = fmodf(boundary_distance, radius); falloff_distance = div % 2 == 0 ? mod : radius - mod; /* Inverts the falloff in the intervals 1 2 5 6 9 10 ... etc. */ @@ -478,17 +691,21 @@ static void sculpt_boundary_falloff_factor_init(SculptSession *ss, } } -SculptBoundary *data_init(Object *object, - Brush *brush, - const PBVHVertRef initial_vertex, - const float radius) +/* Main function to get SculptBoundary data both for brush deformation and viewport preview. Can + * return nullptr if there is no boundary from the given vertex using the given radius. */ +SculptBoundary *data_init(Object *object, Brush *brush, PBVHVertRef initial_vertex, float radius) { - SculptSession *ss = object->sculpt; - if (initial_vertex.i == PBVH_REF_NONE) { return nullptr; } + SculptSession *ss = object->sculpt; + + // XXX force update of BMVert->head.index + if (ss->bm) { + ss->bm->elem_index_dirty |= BM_VERT; + } + SCULPT_vertex_random_access_ensure(ss); SCULPT_boundary_info_ensure(object); @@ -505,17 +722,26 @@ SculptBoundary *data_init(Object *object, return nullptr; } - SculptBoundary *boundary = MEM_cnew(__func__); + SculptBoundary *boundary = (SculptBoundary *)MEM_callocN(sizeof(SculptBoundary) * TSTN, + "Boundary edit data"); + + boundary->deform_target = brush->deform_target; const bool init_boundary_distances = brush ? brush->boundary_falloff_type != BRUSH_BOUNDARY_FALLOFF_CONSTANT : false; - sculpt_boundary_indices_init(ss, boundary, init_boundary_distances, boundary_initial_vertex); - const float boundary_radius = brush ? radius * (1.0f + brush->boundary_offset) : radius; + + sculpt_boundary_indices_init( + object, ss, boundary, init_boundary_distances, boundary_initial_vertex, boundary_radius); + sculpt_boundary_edit_data_init(ss, boundary, boundary_initial_vertex, boundary_radius); + if (ss->cache) { + SCULPT_boundary_build_smoothco(ss, boundary); + } + return boundary; } @@ -524,10 +750,17 @@ void data_free(SculptBoundary *boundary) MEM_SAFE_FREE(boundary->verts); MEM_SAFE_FREE(boundary->edges); MEM_SAFE_FREE(boundary->distance); - MEM_SAFE_FREE(boundary->edit_info); + + MEM_SAFE_FREE(boundary->boundary_dist); + MEM_SAFE_FREE(boundary->boundary_tangents); + MEM_SAFE_FREE(boundary->boundary_closest); + MEM_SAFE_FREE(boundary->smoothco); + MEM_SAFE_FREE(boundary->bend.pivot_positions); MEM_SAFE_FREE(boundary->bend.pivot_rotation_axis); MEM_SAFE_FREE(boundary->slide.directions); + MEM_SAFE_FREE(boundary->circle.origin); + MEM_SAFE_FREE(boundary->circle.radius); MEM_SAFE_FREE(boundary); } @@ -535,20 +768,37 @@ void data_free(SculptBoundary *boundary) * SculptBoundaryEditInfo. They calculate the data using the vertices that have the * max_propagation_steps value and them this data is copied to the rest of the vertices using the * original vertex index. */ -static void sculpt_boundary_bend_data_init(SculptSession *ss, SculptBoundary *boundary) +static void sculpt_boundary_bend_data_init(SculptSession *ss, + SculptBoundary *boundary, + float radius) { const int totvert = SCULPT_vertex_count_get(ss); - boundary->bend.pivot_rotation_axis = static_cast( - MEM_calloc_arrayN(totvert, sizeof(float[3]), __func__)); - boundary->bend.pivot_positions = static_cast( - MEM_calloc_arrayN(totvert, sizeof(float[3]), __func__)); + boundary->bend.pivot_rotation_axis = MEM_cnew_array(totvert, __func__); + boundary->bend.pivot_positions = MEM_cnew_array(totvert, __func__); for (int i = 0; i < totvert; i++) { + + if (boundary->edit_info[i].propagation_steps_num != boundary->max_propagation_steps) { + continue; + } + } + + for (int i = 0; i < totvert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + if (boundary->edit_info[i].original_vertex_i == BOUNDARY_VERTEX_NONE) { + continue; + } + if (boundary->edit_info[i].propagation_steps_num != boundary->max_propagation_steps) { continue; } - PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + if (boundary->edit_info[i].original_vertex_i == -1) { + continue; + } + + const float *co1 = SCULPT_vertex_co_get(ss, vertex); float dir[3]; float normal[3]; @@ -557,22 +807,126 @@ static void sculpt_boundary_bend_data_init(SculptSession *ss, SculptBoundary *bo dir, SCULPT_vertex_co_get( ss, BKE_pbvh_index_to_vertex(ss->pbvh, boundary->edit_info[i].original_vertex_i)), - SCULPT_vertex_co_get(ss, vertex)); + co1); + + normalize_v3(dir); + + float olddir[3]; + copy_v3_v3(olddir, dir); + + if (boundary->boundary_dist[i] != FLT_MAX) { + zero_v3(dir); + copy_v3_v3(dir, boundary->boundary_tangents[i]); + + if (dot_v3v3(dir, dir) < 0.00001f) { + sub_v3_v3v3( + dir, + SCULPT_vertex_co_get( + ss, BKE_pbvh_index_to_vertex(ss->pbvh, boundary->edit_info[i].original_vertex_i)), + SCULPT_vertex_co_get(ss, vertex)); + } + } + else { + // continue; + } + cross_v3_v3v3( boundary->bend.pivot_rotation_axis[boundary->edit_info[i].original_vertex_i], dir, normal); normalize_v3(boundary->bend.pivot_rotation_axis[boundary->edit_info[i].original_vertex_i]); - copy_v3_v3(boundary->bend.pivot_positions[boundary->edit_info[i].original_vertex_i], - SCULPT_vertex_co_get(ss, vertex)); + + float pos[3]; + + copy_v3_v3(pos, co1); + + copy_v3_v3(boundary->bend.pivot_positions[boundary->edit_info[i].original_vertex_i], pos); + boundary->bend.pivot_positions[boundary->edit_info[i].original_vertex_i][3] = 1.0f; } for (int i = 0; i < totvert; i++) { + if (boundary->bend.pivot_positions[i][3] > 1.0f) { + mul_v3_fl(boundary->bend.pivot_positions[i], 1.0f / boundary->bend.pivot_positions[i][3]); + boundary->bend.pivot_positions[i][3] = 1.0f; + } + } + + // fix any remaining boundaries without pivots + for (int vi = 0; vi < boundary->verts_num; vi++) { + PBVHVertRef v = boundary->verts[vi]; + const float *co1 = SCULPT_vertex_co_get(ss, v); + int i = BKE_pbvh_vertex_to_index(ss->pbvh, v); + + if (boundary->bend.pivot_positions[i][3] != 0.0f) { + continue; + } + + float minlen = FLT_MAX; + + // nasty inner loop here + for (int j = 0; j < totvert; j++) { + if (boundary->edit_info[j].propagation_steps_num != boundary->max_propagation_steps) { + continue; + } + + PBVHVertRef v2 = BKE_pbvh_index_to_vertex(ss->pbvh, j); + const float *co2 = SCULPT_vertex_co_get(ss, v2); + + float len = len_v3v3(co2, co1); + + if (len < minlen) { + minlen = len; + copy_v3_v3(boundary->bend.pivot_positions[i], co2); + boundary->bend.pivot_positions[i][3] = 1.0f; + } + } + } + + for (int i = 0; i < totvert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + const float *co1 = SCULPT_vertex_co_get(ss, vertex); + float dir[3]; + if (boundary->edit_info[i].propagation_steps_num == BOUNDARY_STEPS_NONE) { continue; } - copy_v3_v3(boundary->bend.pivot_positions[i], - boundary->bend.pivot_positions[boundary->edit_info[i].original_vertex_i]); - copy_v3_v3(boundary->bend.pivot_rotation_axis[i], - boundary->bend.pivot_rotation_axis[boundary->edit_info[i].original_vertex_i]); + float pos[3], oco[3]; + copy_v3_v3(pos, boundary->bend.pivot_positions[boundary->edit_info[i].original_vertex_i]); + copy_v3_v3( + oco, + SCULPT_vertex_co_get( + ss, BKE_pbvh_index_to_vertex(ss->pbvh, boundary->edit_info[i].original_vertex_i))); + + if (boundary->boundary_dist[i] != FLT_MAX) { + float no[3]; + + SCULPT_vertex_normal_get(ss, vertex, no); + + // snap to radial plane + cross_v3_v3v3(dir, no, boundary->boundary_tangents[i]); + normalize_v3(dir); + //* + + sub_v3_v3(pos, oco); + normalize_v3(pos); + mul_v3_fl(pos, radius); + add_v3_v3(pos, oco); + + sub_v3_v3(pos, co1); + madd_v3_v3fl(pos, dir, -dot_v3v3(dir, pos)); + add_v3_v3(pos, co1); + + //*/ + + copy_v3_v3(boundary->bend.pivot_rotation_axis[i], dir); + } + else { + zero_v3(dir); + + // printf("boundary info missing tangent\n"); + copy_v3_v3(boundary->bend.pivot_rotation_axis[i], + boundary->bend.pivot_rotation_axis[boundary->edit_info[i].original_vertex_i]); + } + + copy_v3_v3(boundary->bend.pivot_positions[i], pos); } } @@ -583,14 +937,18 @@ static void sculpt_boundary_slide_data_init(SculptSession *ss, SculptBoundary *b MEM_calloc_arrayN(totvert, sizeof(float[3]), "slide directions")); for (int i = 0; i < totvert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + if (boundary->edit_info[i].propagation_steps_num != boundary->max_propagation_steps) { continue; } + sub_v3_v3v3( boundary->slide.directions[boundary->edit_info[i].original_vertex_i], SCULPT_vertex_co_get( ss, BKE_pbvh_index_to_vertex(ss->pbvh, boundary->edit_info[i].original_vertex_i)), - SCULPT_vertex_co_get(ss, BKE_pbvh_index_to_vertex(ss->pbvh, i))); + SCULPT_vertex_co_get(ss, vertex)); + normalize_v3(boundary->slide.directions[boundary->edit_info[i].original_vertex_i]); } @@ -603,6 +961,58 @@ static void sculpt_boundary_slide_data_init(SculptSession *ss, SculptBoundary *b } } +static void do_boundary_brush_circle_task(Object *ob, const Brush * /*brush*/, PBVHNode *node) +{ + SculptSession *ss = ob->sculpt; + const int symmetry_pass = ss->cache->mirror_symmetry_pass; + const SculptBoundary *boundary = ss->cache->boundaries[symmetry_pass]; + const ePaintSymmetryFlags symm = SCULPT_mesh_symmetry_xyz_get(ob); + + const float strength = ss->cache->bstrength; + + PBVHVertexIter vd; + SculptOrigVertData orig_data; + SCULPT_orig_vert_data_init(&orig_data, ob, node, undo::Type::Position); + + auto_mask::NodeData automask_data = auto_mask::node_begin( + *ob, ss->cache->automasking.get(), *node); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { + if (boundary->edit_info[vd.index].propagation_steps_num == -1) { + continue; + } + + SCULPT_orig_vert_data_update(&orig_data, vd.vertex); + if (!SCULPT_check_vertex_pivot_symmetry(orig_data.co, boundary->initial_vertex_position, symm)) + { + continue; + } + + auto_mask::node_update(automask_data, vd); + + const int propagation_steps = boundary->edit_info[vd.index].propagation_steps_num; + float *circle_origin = boundary->circle.origin[propagation_steps]; + float circle_disp[3]; + sub_v3_v3v3(circle_disp, circle_origin, orig_data.co); + normalize_v3(circle_disp); + mul_v3_fl(circle_disp, -boundary->circle.radius[propagation_steps]); + float target_circle_co[3]; + add_v3_v3v3(target_circle_co, circle_origin, circle_disp); + + float disp[3]; + sub_v3_v3v3(disp, target_circle_co, vd.co); + float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, boundary->deform_target, &vd); + const float mask = 1.0f - vd.mask; + const float automask = auto_mask::factor_get( + ss->cache->automasking.get(), ss, vd.vertex, &automask_data); + madd_v3_v3v3fl(target_co, + vd.co, + disp, + boundary->edit_info[vd.index].strength_factor * mask * automask * strength); + } + BKE_pbvh_vertex_iter_end; +} + static void sculpt_boundary_twist_data_init(SculptSession *ss, SculptBoundary *boundary) { zero_v3(boundary->twist.pivot_position); @@ -625,6 +1035,51 @@ static void sculpt_boundary_twist_data_init(SculptSession *ss, SculptBoundary *b MEM_freeN(face_verts); } +static void sculpt_boundary_circle_data_init(SculptSession *ss, SculptBoundary *boundary) +{ + + const int totvert = SCULPT_vertex_count_get(ss); + const int totcircles = boundary->max_propagation_steps + 1; + + boundary->circle.radius = MEM_cnew_array(totcircles, "radius"); + boundary->circle.origin = MEM_cnew_array(totcircles, "origin"); + + int *count = MEM_cnew_array(totcircles, "count"); + for (int i = 0; i < totvert; i++) { + const int propagation_step_index = boundary->edit_info[i].propagation_steps_num; + if (propagation_step_index == -1) { + continue; + } + + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + add_v3_v3(boundary->circle.origin[propagation_step_index], SCULPT_vertex_co_get(ss, vertex)); + count[propagation_step_index]++; + } + + for (int i = 0; i < totcircles; i++) { + mul_v3_fl(boundary->circle.origin[i], 1.0f / count[i]); + } + + for (int i = 0; i < totvert; i++) { + const int propagation_step_index = boundary->edit_info[i].propagation_steps_num; + if (propagation_step_index == -1) { + continue; + } + + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + boundary->circle.radius[propagation_step_index] += len_v3v3( + boundary->circle.origin[propagation_step_index], SCULPT_vertex_co_get(ss, vertex)); + } + + for (int i = 0; i < totcircles; i++) { + boundary->circle.radius[i] *= 1.0f / count[i]; + } + + MEM_freeN(count); +} + static float sculpt_boundary_displacement_from_grab_delta_get(SculptSession *ss, SculptBoundary *boundary) { @@ -638,7 +1093,7 @@ static float sculpt_boundary_displacement_from_grab_delta_get(SculptSession *ss, return dist_signed_to_plane_v3(pos, plane); } -static void do_boundary_brush_bend_task(Object *ob, const Brush *brush, PBVHNode *node) +static void do_boundary_brush_bend_task(Object *ob, const Brush * /*brush*/, PBVHNode *node) { SculptSession *ss = ob->sculpt; const int symm_area = ss->cache->mirror_symmetry_pass; @@ -667,7 +1122,7 @@ static void do_boundary_brush_bend_task(Object *ob, const Brush *brush, PBVHNode } auto_mask::node_update(automask_data, vd); - SCULPT_orig_vert_data_update(&orig_data, &vd); + SCULPT_orig_vert_data_update(&orig_data, vd.vertex); if (!SCULPT_check_vertex_pivot_symmetry(orig_data.co, boundary->initial_vertex_position, symm)) { continue; @@ -677,7 +1132,8 @@ static void do_boundary_brush_bend_task(Object *ob, const Brush *brush, PBVHNode const float automask = auto_mask::factor_get( ss->cache->automasking.get(), ss, vd.vertex, &automask_data); float t_orig_co[3]; - float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd); + float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, boundary->deform_target, &vd); + sub_v3_v3v3(t_orig_co, orig_data.co, boundary->bend.pivot_positions[vd.index]); rotate_v3_v3v3fl(target_co, t_orig_co, @@ -688,7 +1144,7 @@ static void do_boundary_brush_bend_task(Object *ob, const Brush *brush, PBVHNode BKE_pbvh_vertex_iter_end; } -static void do_boundary_brush_slide_task(Object *ob, const Brush *brush, PBVHNode *node) +static void do_boundary_brush_slide_task(Object *ob, const Brush * /*brush*/, PBVHNode *node) { SculptSession *ss = ob->sculpt; const int symm_area = ss->cache->mirror_symmetry_pass; @@ -711,7 +1167,7 @@ static void do_boundary_brush_slide_task(Object *ob, const Brush *brush, PBVHNod } auto_mask::node_update(automask_data, vd); - SCULPT_orig_vert_data_update(&orig_data, &vd); + SCULPT_orig_vert_data_update(&orig_data, vd.vertex); if (!SCULPT_check_vertex_pivot_symmetry(orig_data.co, boundary->initial_vertex_position, symm)) { continue; @@ -720,7 +1176,7 @@ static void do_boundary_brush_slide_task(Object *ob, const Brush *brush, PBVHNod const float mask = 1.0f - vd.mask; const float automask = auto_mask::factor_get( ss->cache->automasking.get(), ss, vd.vertex, &automask_data); - float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd); + float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, boundary->deform_target, &vd); madd_v3_v3v3fl(target_co, orig_data.co, boundary->slide.directions[vd.index], @@ -730,7 +1186,7 @@ static void do_boundary_brush_slide_task(Object *ob, const Brush *brush, PBVHNod BKE_pbvh_vertex_iter_end; } -static void do_boundary_brush_inflate_task(Object *ob, const Brush *brush, PBVHNode *node) +static void do_boundary_brush_inflate_task(Object *ob, const Brush * /*brush*/, PBVHNode *node) { SculptSession *ss = ob->sculpt; const int symm_area = ss->cache->mirror_symmetry_pass; @@ -753,7 +1209,7 @@ static void do_boundary_brush_inflate_task(Object *ob, const Brush *brush, PBVHN } auto_mask::node_update(automask_data, vd); - SCULPT_orig_vert_data_update(&orig_data, &vd); + SCULPT_orig_vert_data_update(&orig_data, vd.vertex); if (!SCULPT_check_vertex_pivot_symmetry(orig_data.co, boundary->initial_vertex_position, symm)) { continue; @@ -762,7 +1218,10 @@ static void do_boundary_brush_inflate_task(Object *ob, const Brush *brush, PBVHN const float mask = 1.0f - vd.mask; const float automask = auto_mask::factor_get( ss->cache->automasking.get(), ss, vd.vertex, &automask_data); - float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd); + float normal[3]; + copy_v3_v3(normal, orig_data.no); + + float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, boundary->deform_target, &vd); madd_v3_v3v3fl(target_co, orig_data.co, orig_data.no, @@ -772,7 +1231,7 @@ static void do_boundary_brush_inflate_task(Object *ob, const Brush *brush, PBVHN BKE_pbvh_vertex_iter_end; } -static void do_boundary_brush_grab_task(Object *ob, const Brush *brush, PBVHNode *node) +static void do_boundary_brush_grab_task(Object *ob, const Brush * /*brush*/, PBVHNode *node) { SculptSession *ss = ob->sculpt; const int symm_area = ss->cache->mirror_symmetry_pass; @@ -793,7 +1252,7 @@ static void do_boundary_brush_grab_task(Object *ob, const Brush *brush, PBVHNode } auto_mask::node_update(automask_data, vd); - SCULPT_orig_vert_data_update(&orig_data, &vd); + SCULPT_orig_vert_data_update(&orig_data, vd.vertex); if (!SCULPT_check_vertex_pivot_symmetry(orig_data.co, boundary->initial_vertex_position, symm)) { continue; @@ -802,7 +1261,7 @@ static void do_boundary_brush_grab_task(Object *ob, const Brush *brush, PBVHNode const float mask = 1.0f - vd.mask; const float automask = auto_mask::factor_get( ss->cache->automasking.get(), ss, vd.vertex, &automask_data); - float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd); + float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, boundary->deform_target, &vd); madd_v3_v3v3fl(target_co, orig_data.co, ss->cache->grab_delta_symmetry, @@ -811,7 +1270,7 @@ static void do_boundary_brush_grab_task(Object *ob, const Brush *brush, PBVHNode BKE_pbvh_vertex_iter_end; } -static void do_boundary_brush_twist_task(Object *ob, const Brush *brush, PBVHNode *node) +static void do_boundary_brush_twist_task(Object *ob, const Brush * /*brush*/, PBVHNode *node) { SculptSession *ss = ob->sculpt; const int symm_area = ss->cache->mirror_symmetry_pass; @@ -840,7 +1299,7 @@ static void do_boundary_brush_twist_task(Object *ob, const Brush *brush, PBVHNod } auto_mask::node_update(automask_data, vd); - SCULPT_orig_vert_data_update(&orig_data, &vd); + SCULPT_orig_vert_data_update(&orig_data, vd.vertex); if (!SCULPT_check_vertex_pivot_symmetry(orig_data.co, boundary->initial_vertex_position, symm)) { continue; @@ -850,7 +1309,7 @@ static void do_boundary_brush_twist_task(Object *ob, const Brush *brush, PBVHNod const float automask = auto_mask::factor_get( ss->cache->automasking.get(), ss, vd.vertex, &automask_data); float t_orig_co[3]; - float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd); + float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, boundary->deform_target, &vd); sub_v3_v3v3(t_orig_co, orig_data.co, boundary->twist.pivot_position); rotate_v3_v3v3fl(target_co, t_orig_co, @@ -861,7 +1320,7 @@ static void do_boundary_brush_twist_task(Object *ob, const Brush *brush, PBVHNod BKE_pbvh_vertex_iter_end; } -static void do_boundary_brush_smooth_task(Object *ob, const Brush *brush, PBVHNode *node) +static void do_boundary_brush_smooth_task(Object *ob, const Brush * /*brush*/, PBVHNode *node) { SculptSession *ss = ob->sculpt; const int symmetry_pass = ss->cache->mirror_symmetry_pass; @@ -879,7 +1338,7 @@ static void do_boundary_brush_smooth_task(Object *ob, const Brush *brush, PBVHNo continue; } - SCULPT_orig_vert_data_update(&orig_data, &vd); + SCULPT_orig_vert_data_update(&orig_data, vd.vertex); if (!SCULPT_check_vertex_pivot_symmetry(orig_data.co, boundary->initial_vertex_position, symm)) { continue; @@ -905,18 +1364,120 @@ static void do_boundary_brush_smooth_task(Object *ob, const Brush *brush, PBVHNo const float mask = 1.0f - vd.mask; mul_v3_v3fl(avg, coord_accum, 1.0f / total_neighbors); sub_v3_v3v3(disp, avg, vd.co); - float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd); + float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, boundary->deform_target, &vd); madd_v3_v3v3fl( target_co, vd.co, disp, boundary->edit_info[vd.index].strength_factor * mask * strength); } BKE_pbvh_vertex_iter_end; } +static void SCULPT_boundary_autosmooth(SculptSession *ss, SculptBoundary *boundary) +{ + const int max_iterations = 4; + const float fract = 1.0f / max_iterations; + float bstrength = ss->cache->brush->autosmooth_factor; + + CLAMP(bstrength, 0.0f, 1.0f); + + const int count = (int)(bstrength * max_iterations); + const float last = max_iterations * (bstrength - count * fract); + + const float boundary_radius = ss->cache->radius * (1.0f + ss->cache->brush->boundary_offset) * + ss->cache->brush->autosmooth_radius_factor; + + BKE_curvemapping_init(ss->cache->brush->curve); + + Vector nodes = blender::bke::pbvh::search_gather(ss->pbvh, {}); + + float projection = ss->cache->brush->autosmooth_projection; + float hard_corner_pin = BKE_brush_hard_corner_pin_get(ss->scene, ss->cache->brush); + + for (int iteration = 0; iteration <= count; iteration++) { + for (int i = 0; i < nodes.size(); i++) { + const float strength = (iteration != count) ? 1.0f : last; + + PBVHNode *node = nodes[i]; + PBVHVertexIter vd; + + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { + if (boundary->boundary_dist[vd.index] == FLT_MAX) { + continue; + } + + if (boundary->edit_info[vd.index].propagation_steps_num == BOUNDARY_STEPS_NONE) { + continue; + } + + float fac = boundary->boundary_dist[vd.index] / boundary_radius; + + if (fac > 1.0f) { + continue; + } + + fac = BKE_brush_curve_strength(ss->cache->brush, fac, 1.0f); + + float sco[3]; + + smooth::neighbor_coords_average_interior( + ss, sco, vd.vertex, projection, hard_corner_pin, true); + + float *co = SCULPT_brush_deform_target_vertex_co_get(ss, boundary->deform_target, &vd); + + interp_v3_v3v3(co, co, sco, strength * fac); + BKE_pbvh_node_mark_update(node); + } + BKE_pbvh_vertex_iter_end; + } + } +} + +static void SCULPT_boundary_build_smoothco(SculptSession *ss, SculptBoundary *boundary) +{ + const int totvert = SCULPT_vertex_count_get(ss); + + boundary->smoothco = MEM_cnew_array(totvert, "boundary->smoothco"); + + float projection = ss->cache->brush->autosmooth_projection; + float hard_corner_pin = BKE_brush_hard_corner_pin_get(ss->scene, ss->cache->brush); + + Vector nodes = blender::bke::pbvh::search_gather(ss->pbvh, {}); + + for (int iteration = 0; iteration < 3; iteration++) { + for (int i = 0; i < nodes.size(); i++) { + PBVHNode *node = nodes[i]; + PBVHVertexIter vd; + + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { + if (boundary->boundary_dist[vd.index] == FLT_MAX) { + continue; + } + + float sco[3]; + + smooth::neighbor_coords_average_interior( + ss, sco, vd.vertex, projection, hard_corner_pin, true); + + float *co = SCULPT_brush_deform_target_vertex_co_get(ss, boundary->deform_target, &vd); + + interp_v3_v3v3(sco, sco, co, 0.25); + BKE_pbvh_node_mark_update(node); + + copy_v3_v3(boundary->smoothco[vd.index], sco); + } + BKE_pbvh_vertex_iter_end; + } + } +} + +/* Main Brush Function. */ void do_boundary_brush(Sculpt *sd, Object *ob, Span nodes) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); + const float radius = ss->cache->radius; + const float boundary_radius = brush ? radius * brush->boundary_offset : radius; + const ePaintSymmetryFlags symm_area = ss->cache->mirror_symmetry_pass; if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { @@ -935,10 +1496,9 @@ void do_boundary_brush(Sculpt *sd, Object *ob, Span nodes) ob, brush, initial_vertex, ss->cache->initial_radius); if (ss->cache->boundaries[symm_area]) { - switch (brush->boundary_deform_type) { case BRUSH_BOUNDARY_DEFORM_BEND: - sculpt_boundary_bend_data_init(ss, ss->cache->boundaries[symm_area]); + sculpt_boundary_bend_data_init(ss, ss->cache->boundaries[symm_area], boundary_radius); break; case BRUSH_BOUNDARY_DEFORM_EXPAND: sculpt_boundary_slide_data_init(ss, ss->cache->boundaries[symm_area]); @@ -946,6 +1506,9 @@ void do_boundary_brush(Sculpt *sd, Object *ob, Span nodes) case BRUSH_BOUNDARY_DEFORM_TWIST: sculpt_boundary_twist_data_init(ss, ss->cache->boundaries[symm_area]); break; + case BRUSH_BOUNDARY_DEFORM_CIRCLE: + sculpt_boundary_circle_data_init(ss, ss->cache->boundaries[symm_area]); + break; case BRUSH_BOUNDARY_DEFORM_INFLATE: case BRUSH_BOUNDARY_DEFORM_GRAB: /* Do nothing. These deform modes don't need any extra data to be precomputed. */ @@ -953,7 +1516,32 @@ void do_boundary_brush(Sculpt *sd, Object *ob, Span nodes) } sculpt_boundary_falloff_factor_init( - ss, ss->cache->boundaries[symm_area], brush, ss->cache->initial_radius); + ss, sd, ss->cache->boundaries[symm_area], brush, ss->cache->initial_radius); + } + + if (ss->bm && ss->cache->boundaries[symm_area] && + ss->cache->boundaries[symm_area]->boundary_dist) + { + Vector nodes2 = blender::bke::pbvh::search_gather(ss->pbvh, {}); + + for (int i = 0; i < nodes2.size(); i++) { + PBVHNode *node = nodes2[i]; + PBVHVertexIter vd; + + bool ok = false; + + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { + if (ss->cache->boundaries[symm_area]->boundary_dist[vd.index] != FLT_MAX) { + ok = true; + break; + } + } + BKE_pbvh_vertex_iter_end; + + if (ok) { + undo::ensure_dyntopo_node_undo(ob, node, undo::Type::Position); + } + } } } @@ -1005,6 +1593,19 @@ void do_boundary_brush(Sculpt *sd, Object *ob, Span nodes) } }); break; + case BRUSH_BOUNDARY_DEFORM_CIRCLE: + threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { + for (const int i : range) { + do_boundary_brush_circle_task(ob, brush, nodes[i]); + } + }); + break; + } + + if (brush->autosmooth_factor > 0.0f) { + bke::pbvh::update_normals(*ss->pbvh, ss->subdiv_ccg); + + SCULPT_boundary_autosmooth(ss, ss->cache->boundaries[symm_area]); } } diff --git a/source/blender/editors/sculpt_paint/sculpt_brush_types.cc b/source/blender/editors/sculpt_paint/sculpt_brush_types.cc index ac4b955853d..9acba58fd76 100644 --- a/source/blender/editors/sculpt_paint/sculpt_brush_types.cc +++ b/source/blender/editors/sculpt_paint/sculpt_brush_types.cc @@ -41,6 +41,7 @@ #include #include +using namespace blender::bke::paint; using blender::float3; using blender::MutableSpan; using blender::Span; @@ -933,7 +934,8 @@ static void do_clay_strips_brush_task(Object *ob, *ob, ss->cache->automasking.get(), *node); BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - if (!SCULPT_brush_test_cube(&test, vd.co, mat, brush->tip_roundness, brush->tip_scale_x)) { + if (!SCULPT_brush_test_cube(&test, vd.co, mat, brush->tip_roundness, brush->tip_scale_x, true)) + { continue; } @@ -1153,6 +1155,7 @@ static void do_snake_hook_brush_task(Object *ob, auto_mask::factor_get(ss->cache->automasking.get(), ss, vd.vertex, &automask_data)); copy_v3_v3(proxy[vd.i], disp); } + BKE_sculpt_sharp_boundary_flag_update(ss, vd.vertex); } BKE_pbvh_vertex_iter_end; } @@ -1210,7 +1213,7 @@ static void do_thumb_brush_task(Object *ob, const Brush *brush, const float *con *ob, ss->cache->automasking.get(), *node); BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); + SCULPT_orig_vert_data_update(&orig_data, vd.vertex); if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { continue; @@ -1274,7 +1277,7 @@ static void do_rotate_brush_task(Object *ob, const Brush *brush, const float ang *ob, ss->cache->automasking.get(), *node); BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); + SCULPT_orig_vert_data_update(&orig_data, vd.vertex); if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { continue; @@ -1324,8 +1327,7 @@ static void do_layer_brush_task(Object *ob, Sculpt *sd, const Brush *brush, PBVH using namespace blender::ed::sculpt_paint; SculptSession *ss = ob->sculpt; - const bool use_persistent_base = !ss->bm && ss->attrs.persistent_co && - brush->flag & BRUSH_PERSISTENT; + const bool use_persistent_base = ss->attrs.persistent_co && brush->flag & BRUSH_PERSISTENT; PBVHVertexIter vd; SculptOrigVertData orig_data; @@ -1341,7 +1343,7 @@ static void do_layer_brush_task(Object *ob, Sculpt *sd, const Brush *brush, PBVH *ob, ss->cache->automasking.get(), *node); BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); + SCULPT_orig_vert_data_update(&orig_data, vd.vertex); if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { continue; @@ -1359,13 +1361,23 @@ static void do_layer_brush_task(Object *ob, Sculpt *sd, const Brush *brush, PBVH thread_id, &automask_data); - const int vi = vd.index; float *disp_factor; if (use_persistent_base) { - disp_factor = (float *)SCULPT_vertex_attr_get(vd.vertex, ss->attrs.persistent_disp); + disp_factor = vertex_attr_ptr(vd.vertex, ss->attrs.persistent_disp); } else { - disp_factor = &ss->cache->layer_displacement_factor[vi]; + disp_factor = vertex_attr_ptr(vd.vertex, ss->attrs.layer_displayment); + + if (blender::bke::sculpt::stroke_id_test(ss, vd.vertex, STROKEID_USER_LAYER_BRUSH)) { + *disp_factor = 0.0f; + } + } + + PBVH_CHECK_NAN1(*disp_factor); + + if (isnan(*disp_factor)) { + vd.bm_vert->head.hflag |= BM_ELEM_SELECT; + continue; } /* When using persistent base, the layer brush (holding Control) invert mode resets the @@ -1383,6 +1395,8 @@ static void do_layer_brush_task(Object *ob, Sculpt *sd, const Brush *brush, PBVH const float clamp_mask = 1.0f - vd.mask; *disp_factor = clamp_f(*disp_factor, -clamp_mask, clamp_mask); + PBVH_CHECK_NAN1(*disp_factor); + float final_co[3]; float normal[3]; @@ -1403,7 +1417,10 @@ static void do_layer_brush_task(Object *ob, Sculpt *sd, const Brush *brush, PBVH mul_v3_fl(vdisp, fabsf(fade)); add_v3_v3v3(final_co, vd.co, vdisp); + PBVH_CHECK_NAN(final_co); SCULPT_clip(sd, ss, vd.co, final_co); + + PBVH_CHECK_NAN1(*disp_factor); } BKE_pbvh_vertex_iter_end; } @@ -1414,11 +1431,15 @@ void SCULPT_do_layer_brush(Sculpt *sd, Object *ob, Span nodes) SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); - if (ss->cache->layer_displacement_factor == nullptr) { - ss->cache->layer_displacement_factor = MEM_cnew_array(SCULPT_vertex_count_get(ss), - __func__); + const bool use_persistent_base = ss->attrs.persistent_co && brush->flag & BRUSH_PERSISTENT; + if (!use_persistent_base && !ss->attrs.layer_displayment) { + SculptAttributeParams params = {}; + + ss->attrs.layer_displayment = BKE_sculpt_attribute_ensure( + ob, AttrDomain::Point, CD_PROP_FLOAT, SCULPT_ATTRIBUTE_NAME(layer_displayment), ¶ms); } + SCULPT_stroke_id_ensure(ob); threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { for (const int i : range) { do_layer_brush_task(ob, sd, brush, nodes[i]); @@ -1792,7 +1813,7 @@ static void do_grab_brush_task(Object *ob, *ob, ss->cache->automasking.get(), *node); BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); + SCULPT_orig_vert_data_update(&orig_data, vd.vertex); if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { continue; @@ -1822,6 +1843,7 @@ static void do_grab_brush_task(Object *ob, } mul_v3_v3fl(proxy[vd.i], grab_delta, fade); + BKE_sculpt_sharp_boundary_flag_update(ss, vd.vertex); } BKE_pbvh_vertex_iter_end; } @@ -1886,7 +1908,7 @@ static void do_elastic_deform_brush_task(Object *ob, ¶ms, ss->cache->radius, force, 1.0f, brush->elastic_deform_volume_preservation); BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); + SCULPT_orig_vert_data_update(&orig_data, vd.vertex); auto_mask::node_update(automask_data, vd); float final_disp[3]; @@ -1974,7 +1996,7 @@ static void do_draw_sharp_brush_task(Object *ob, *ob, ss->cache->automasking.get(), *node); BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); + SCULPT_orig_vert_data_update(&orig_data, vd.vertex); if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { continue; } @@ -2049,7 +2071,7 @@ static void do_topology_slide_task(Object *ob, const Brush *brush, PBVHNode *nod *ob, ss->cache->automasking.get(), *node); BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); + SCULPT_orig_vert_data_update(&orig_data, vd.vertex); if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { continue; } @@ -2106,54 +2128,63 @@ namespace blender::ed::sculpt_paint::smooth { void relax_vertex(SculptSession *ss, PBVHVertexIter *vd, float factor, - bool filter_boundary_face_sets, + eSculptBoundary boundary_mask, float *r_final_pos) { float smooth_pos[3]; float final_disp[3]; - float boundary_normal[3]; int avg_count = 0; - int neighbor_count = 0; zero_v3(smooth_pos); - zero_v3(boundary_normal); - const bool is_boundary = SCULPT_vertex_is_boundary(ss, vd->vertex); - SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd->vertex, ni) { - neighbor_count++; - if (!filter_boundary_face_sets || - (filter_boundary_face_sets && !face_set::vert_has_unique_face_set(ss, ni.vertex))) - { + eSculptBoundary bset = boundary_mask; + bset |= SCULPT_BOUNDARY_FACE_SET; - /* When the vertex to relax is boundary, use only connected boundary vertices for the average - * position. */ - if (is_boundary) { - if (!SCULPT_vertex_is_boundary(ss, ni.vertex)) { - continue; - } - add_v3_v3(smooth_pos, SCULPT_vertex_co_get(ss, ni.vertex)); - avg_count++; + eSculptCorner corner_mask = eSculptCorner( + int(bset & (SCULPT_BOUNDARY_MESH | SCULPT_BOUNDARY_SHARP_MARK | SCULPT_BOUNDARY_SHARP_ANGLE)) + << SCULPT_CORNER_BIT_SHIFT); - /* Calculate a normal for the constraint plane using the edges of the boundary. */ - float to_neighbor[3]; - sub_v3_v3v3(to_neighbor, SCULPT_vertex_co_get(ss, ni.vertex), vd->co); - normalize_v3(to_neighbor); - add_v3_v3(boundary_normal, to_neighbor); - } - else { - add_v3_v3(smooth_pos, SCULPT_vertex_co_get(ss, ni.vertex)); - avg_count++; - } - } - } - SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); - - /* Don't modify corner vertices. */ - if (neighbor_count <= 2) { + if (SCULPT_vertex_is_corner(ss, vd->vertex, corner_mask)) { copy_v3_v3(r_final_pos, vd->co); return; } + const eSculptBoundary is_boundary = SCULPT_vertex_is_boundary(ss, vd->vertex, bset); + + float boundary_tan_a[3]; + float boundary_tan_b[3]; + bool have_boundary_tan_a = false; + + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd->vertex, ni) { + /* When the vertex to relax is boundary, use only connected boundary vertices for the + * average position. */ + if (is_boundary) { + if (SCULPT_vertex_is_boundary(ss, ni.vertex, bset) == SCULPT_BOUNDARY_NONE) { + continue; + } + add_v3_v3(smooth_pos, SCULPT_vertex_co_get(ss, ni.vertex)); + avg_count++; + + /* Calculate a normal for the constraint plane using the edges of the boundary. */ + float to_neighbor[3]; + sub_v3_v3v3(to_neighbor, SCULPT_vertex_co_get(ss, ni.vertex), vd->co); + normalize_v3(to_neighbor); + + if (!have_boundary_tan_a) { + copy_v3_v3(boundary_tan_a, to_neighbor); + have_boundary_tan_a = true; + } + else { + copy_v3_v3(boundary_tan_b, to_neighbor); + } + } + else { + add_v3_v3(smooth_pos, SCULPT_vertex_co_get(ss, ni.vertex)); + avg_count++; + } + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + if (avg_count > 0) { mul_v3_fl(smooth_pos, 1.0f / avg_count); } @@ -2166,8 +2197,9 @@ void relax_vertex(SculptSession *ss, float smooth_closest_plane[3]; float vno[3]; - if (is_boundary && avg_count == 2) { - normalize_v3_v3(vno, boundary_normal); + if ((is_boundary) && avg_count == 2 && fabsf(dot_v3v3(boundary_tan_a, boundary_tan_b)) < 0.99f) { + cross_v3_v3v3(vno, boundary_tan_a, boundary_tan_b); + normalize_v3(vno); } else { SCULPT_vertex_normal_get(ss, vd->vertex, vno); @@ -2212,25 +2244,48 @@ static void do_topology_relax_task(Object *ob, const Brush *brush, PBVHNode *nod auto_mask::NodeData automask_data = auto_mask::node_begin( *ob, ss->cache->automasking.get(), *node); + eAttrCorrectMode distort_correction_mode = smooth::need_reproject(ss); + bool weighted = brush->flag2 & BRUSH_SMOOTH_USE_AREA_WEIGHT; + + if (weighted) { + BKE_pbvh_check_tri_areas(ss->pbvh, node); + } + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); + SCULPT_orig_vert_data_update(&orig_data, vd.vertex); if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { continue; } auto_mask::node_update(automask_data, vd); - const float fade = SCULPT_brush_strength_factor(ss, - brush, - orig_data.co, - sqrtf(test.dist), - orig_data.no, - nullptr, - vd.mask, - vd.vertex, - thread_id, - &automask_data); + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + orig_data.co, + sqrtf(test.dist), + orig_data.no, + nullptr, + vd.mask, + vd.vertex, + thread_id, + &automask_data); - smooth::relax_vertex(ss, &vd, fade * bstrength, false, vd.co); + float3 startco = vd.co; + float3 startno; + float3 avg; + + SCULPT_vertex_normal_get(ss, vd.vertex, startno); + + /* SCULPT_relax_vertex(ss, &vd, fade , SCULPT_BOUNDARY_MESH, vd.co); */ + + smooth::neighbor_coords_average_interior( + ss, avg, vd.vertex, 0.98f, 1.0f, weighted, false, fade); + + interp_v3_v3v3(vd.co, vd.co, avg, fade); + BKE_sculpt_sharp_boundary_flag_update(ss, vd.vertex); + + if (distort_correction_mode & ~UNDISTORT_RELAX_UVS) { + BKE_sculpt_reproject_cdata(ss, vd.vertex, startco, startno, ss->distort_correction_mode); + } } BKE_pbvh_vertex_iter_end; } @@ -2245,10 +2300,16 @@ void SCULPT_do_slide_relax_brush(Sculpt *sd, Object *ob, Span nodes) return; } + SCULPT_boundary_info_ensure(ob); + BKE_curvemapping_init(brush->curve); if (ss->cache->alt_smooth) { SCULPT_boundary_info_ensure(ob); + if (brush->flag2 & BRUSH_SMOOTH_USE_AREA_WEIGHT) { + BKE_pbvh_face_areas_begin(ob, ss->pbvh); + } + for (int i = 0; i < 4; i++) { threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { for (const int i : range) { @@ -2321,6 +2382,9 @@ void SCULPT_do_displacement_eraser_brush(Sculpt *sd, Object *ob, Spanpaint); BKE_curvemapping_init(brush->curve); + if (BKE_pbvh_type(ob->sculpt->pbvh) != PBVH_GRIDS) { + return; + } threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { for (const int i : range) { do_displacement_eraser_brush_task(ob, brush, nodes[i]); @@ -2438,6 +2502,10 @@ void SCULPT_do_displacement_smear_brush(Sculpt *sd, Object *ob, Span Brush *brush = BKE_paint_brush(&sd->paint); SculptSession *ss = ob->sculpt; + if (BKE_pbvh_type(ss->pbvh) != PBVH_GRIDS) { + return; + } + BKE_curvemapping_init(brush->curve); const int totvert = SCULPT_vertex_count_get(ss); @@ -2470,16 +2538,34 @@ void SCULPT_do_displacement_smear_brush(Sculpt *sd, Object *ob, Span /** \} */ +static void update_curvatures_task_cb_ex(Object *ob, const Brush *brush, PBVHNode *node) +{ + SculptSession *ss = ob->sculpt; + BKE_pbvh_check_tri_areas(ss->pbvh, node); + + if (brush->flag2 & BRUSH_CURVATURE_RAKE) { + SCULPT_curvature_begin(ss, node, true); + } +} + /* -------------------------------------------------------------------- */ /** \name Sculpt Topology Rake (Shared Utility) * \{ */ -static void do_topology_rake_bmesh_task( - Object *ob, Sculpt *sd, const Brush *brush, const float strength, PBVHNode *node) +static void do_topology_rake_bmesh_task(Object *ob, + Sculpt *sd, + const Brush *brush, + const float strength, + bool smooth_origco, + PBVHNode *node) { using namespace blender::ed::sculpt_paint; SculptSession *ss = ob->sculpt; + const bool use_curvature = brush->flag2 & BRUSH_CURVATURE_RAKE; + const eAttrCorrectMode distort_correction_mode = smooth::need_reproject(ss); + float hard_corner_pin = BKE_brush_hard_corner_pin_get(ss->scene, brush); + float direction[3]; copy_v3_v3(direction, ss->cache->grab_delta_symmetry); @@ -2505,55 +2591,140 @@ static void do_topology_rake_bmesh_task( *ob, ss->cache->automasking.get(), *node); PBVHVertexIter vd; + bool modified = false; + + const float projection = brush->autosmooth_projection; + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { + float direction2[3]; + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { continue; } + if (use_curvature) { + SCULPT_curvature_dir_get(ss, vd.vertex, direction2, true); + } + else { + copy_v3_v3(direction2, direction); + } + + if (is_zero_v3(direction2)) { + continue; + } + auto_mask::node_update(automask_data, vd); - const float fade = bstrength * - SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - vd.mask, - vd.vertex, - thread_id, - &automask_data) * - ss->cache->pressure; + modified = true; + + float fade = SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask, + vd.vertex, + thread_id, + &automask_data); + + /* Make brush falloff less sharp. */ + fade = powf(fade, 1.0f / 3.0f); + fade *= bstrength; + + float oldco[3]; + float oldno[3]; + copy_v3_v3(oldco, vd.co); + SCULPT_vertex_normal_get(ss, vd.vertex, oldno); float avg[3], val[3]; - smooth::bmesh_four_neighbor_average(avg, direction, vd.bm_vert); + int cd_temp = ss->attrs.rake_temp->bmesh_cd_offset; + + smooth::bmesh_four_neighbor_average(ss, + avg, + direction2, + vd.bm_vert, + projection, + hard_corner_pin, + cd_temp, + true, + false, + fade, + distort_correction_mode & UNDISTORT_RELAX_UVS); sub_v3_v3v3(val, avg, vd.co); - madd_v3_v3v3fl(val, vd.co, val, fade); - SCULPT_clip(sd, ss, vd.co, val); + + if (smooth_origco) { + float origco_avg[3]; + + SCULPT_vertex_check_origdata(ss, vd.vertex); + smooth::bmesh_four_neighbor_average(ss, + origco_avg, + direction2, + vd.bm_vert, + projection, + hard_corner_pin, + cd_temp, + true, + true, + fade, + false); + float *origco = blender::bke::paint::vertex_attr_ptr(vd.vertex, ss->attrs.orig_co); + interp_v3_v3v3(origco, origco, origco_avg, fade); + } + + if (distort_correction_mode & ~UNDISTORT_RELAX_UVS) { + BKE_sculpt_reproject_cdata(ss, vd.vertex, oldco, oldno, ss->distort_correction_mode); + } } BKE_pbvh_vertex_iter_end; + + if (modified) { + BKE_pbvh_node_mark_update(node); + } } void SCULPT_bmesh_topology_rake(Sculpt *sd, Object *ob, Span nodes, float bstrength) { using namespace blender; + using namespace ed::sculpt_paint; + Brush *brush = BKE_paint_brush(&sd->paint); + SculptSession *ss = ob->sculpt; const float strength = clamp_f(bstrength, 0.0f, 1.0f); + smooth::smooth_undo_push(sd, ob, nodes, brush); + /* Interactions increase both strength and quality. */ - const int iterations = 3; + const int iterations = 1; int iteration; const int count = iterations * strength + 1; - const float factor = iterations * strength / count; + const float factor = (iterations * strength / count) * 0.25f; + + if (!ss->attrs.rake_temp) { + SculptAttributeParams params = {}; + ss->attrs.rake_temp = BKE_sculpt_attribute_ensure( + ob, AttrDomain::Point, CD_PROP_COLOR, SCULPT_ATTRIBUTE_NAME(rake_temp), ¶ms); + } + + if (brush->flag2 & BRUSH_CURVATURE_RAKE) { + BKE_sculpt_ensure_curvature_dir(ob); + } for (iteration = 0; iteration <= count; iteration++) { + BKE_pbvh_face_areas_begin(ob, ss->pbvh); threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { for (const int i : range) { - do_topology_rake_bmesh_task(ob, sd, brush, factor, nodes[i]); + update_curvatures_task_cb_ex(ob, brush, nodes[i]); + } + }); + threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { + for (const int i : range) { + do_topology_rake_bmesh_task( + ob, sd, brush, factor, SCULPT_tool_needs_smooth_origco(brush->sculpt_tool), nodes[i]); } }); } @@ -2637,7 +2808,8 @@ void SCULPT_do_mask_brush(Sculpt *sd, Object *ob, Span nodes) SCULPT_do_mask_brush_draw(sd, ob, nodes); break; case BRUSH_MASK_SMOOTH: - smooth::do_smooth_mask_brush(sd, ob, nodes, ss->cache->bstrength); + BKE_sculpt_ensure_origmask(ob); + smooth::do_smooth_brush(sd, ob, nodes, ss->cache->bstrength, true); break; } } diff --git a/source/blender/editors/sculpt_paint/sculpt_cloth.cc b/source/blender/editors/sculpt_paint/sculpt_cloth.cc index 052319187fe..28fd04c4e76 100644 --- a/source/blender/editors/sculpt_paint/sculpt_cloth.cc +++ b/source/blender/editors/sculpt_paint/sculpt_cloth.cc @@ -1103,6 +1103,10 @@ void brush_simulation_init(SculptSession *ss, SimulationData *cloth_sim) const int totverts = SCULPT_vertex_count_get(ss); const bool has_deformation_pos = cloth_sim->deformation_pos != nullptr; const bool has_softbody_pos = cloth_sim->softbody_pos != nullptr; + + SCULPT_vertex_random_access_ensure(ss); + SCULPT_face_random_access_ensure(ss); + for (int i = 0; i < totverts; i++) { PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); @@ -1476,7 +1480,7 @@ static int sculpt_cloth_filter_modal(bContext *C, wmOperator *op, const wmEvent float filter_strength = RNA_float_get(op->ptr, "strength"); if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { - filter::cache_free(ss); + filter::cache_free(ss, ob); undo::push_end(ob); SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COORDS); return OPERATOR_FINISHED; diff --git a/source/blender/editors/sculpt_paint/sculpt_curvature.cc b/source/blender/editors/sculpt_paint/sculpt_curvature.cc new file mode 100644 index 00000000000..a86ebb5d3ac --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_curvature.cc @@ -0,0 +1,202 @@ +/* + * 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) 2021 by Joseph Eagar + * All rights reserved. + * Implements curvature analysis for sculpt tools + */ + +/** \file + * \ingroup edsculpt + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_math_matrix.h" +#include "BLI_math_solvers.h" + +#include "BKE_paint.hh" +#include "BKE_pbvh_api.hh" + +#include "sculpt_intern.hh" + +#include + +using namespace blender::bke::paint; + +/* +If you're working with uniform triangle tesselations, the math for +calculating principle curvatures reduces to doing an eigen decomposition +of the smoothed normal covariance matrix. + +The normal covariance matrix is just: + +nx*nx nx*ny nx*nz +ny*nx ny*ny ny*nz +nz*nx nz*ny nz*nz + +To find principle curvatures, simply subtract neighboring covariance matrices. +You can do this over any number of neighborhood rings to get more accurate result + +*/ + +BLI_INLINE void normal_covariance(float mat[3][3], float no[3]) +{ + mat[0][0] = no[0] * no[0]; + mat[0][1] = no[0] * no[1]; + mat[0][2] = no[0] * no[2]; + mat[1][0] = no[1] * no[0]; + mat[1][1] = no[1] * no[1]; + mat[1][2] = no[1] * no[2]; + mat[2][0] = no[2] * no[0]; + mat[2][1] = no[2] * no[1]; + mat[2][2] = no[2] * no[2]; +} + +bool SCULPT_calc_principle_curvatures(SculptSession *ss, + PBVHVertRef vertex, + SculptCurvatureData *out, + bool useAccurateSolver) +{ + SculptVertexNeighborIter ni; + float nmat[3][3], nmat2[3][3]; + float no[3], no2[3]; + + memset(out, 0, sizeof(SculptCurvatureData)); + + SCULPT_vertex_normal_get(ss, vertex, no); + normal_covariance(nmat, no); + + /* TODO: review the math here. We're deriving the curvature + * via an eigen decomposition of the weighted summed + * normal covariance matrices of the surrounding topology. + */ + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { + SCULPT_vertex_normal_get(ss, ni.vertex, no2); + sub_v3_v3(no2, no); + + SculptVertexNeighborIter ni2; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, ni.vertex, ni2) { + float no3[3]; + SCULPT_vertex_normal_get(ss, ni2.vertex, no3); + + normal_covariance(nmat2, no3); + madd_m3_m3m3fl(nmat, nmat, nmat2, 1.0f / ni2.size); + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni2); + + normal_covariance(nmat2, no2); + madd_m3_m3m3fl(nmat, nmat, nmat2, 1.0f / ni.size); + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + + if (!useAccurateSolver || !BLI_eigen_solve_selfadjoint_m3(nmat, out->ks, out->principle)) { + /* Do simple power solve in one direction. */ + + float t[3]; + float t2[3]; + + SCULPT_vertex_normal_get(ss, vertex, no); + copy_v3_v3(t, no); + + for (int i = 0; i < 25; i++) { + if (i > 0) { + normalize_v3(t); + + if (i > 5 && len_squared_v3v3(t, t2) < 0.000001f) { + break; + } + + copy_v3_v3(t2, t); + } + + mul_m3_v3(nmat, t); + } + + out->ks[1] = normalize_v3(t); + copy_v3_v3(out->principle[1], t); + + cross_v3_v3v3(out->principle[0], out->principle[1], no); + if (dot_v3v3(out->principle[0], out->principle[0]) > FLT_EPSILON * 50.0f) { + normalize_v3(out->principle[0]); + } + else { + zero_v3(out->principle[0]); + } + } + + if (is_zero_v3(out->principle[0])) { + /* Choose an orthoganoal direction. */ + copy_v3_v3(out->principle[0], no); + float axis[3] = {0.0f, 0.0f, 0.0f}; + + if (fabsf(no[0]) > fabs(no[1]) && fabs(no[0]) >= fabs(no[2])) { + axis[1] = 1.0f; + } + else if (fabsf(no[1]) > fabs(no[0]) && fabs(no[1]) >= fabs(no[2])) { + axis[2] = 1.0f; + } + else { + axis[0] = 1.0f; + } + + cross_v3_v3v3(out->principle[0], no, axis); + cross_v3_v3v3(out->principle[1], out->principle[0], no); + copy_v3_v3(out->principle[2], no); + + normalize_v3(out->principle[0]); + normalize_v3(out->principle[1]); + } + + return true; +} + +void SCULPT_curvature_dir_get(SculptSession *ss, + PBVHVertRef v, + float dir[3], + bool useAccurateSolver) +{ + if (BKE_pbvh_type(ss->pbvh) != PBVH_BMESH) { + SculptCurvatureData curv; + SCULPT_calc_principle_curvatures(ss, v, &curv, useAccurateSolver); + + copy_v3_v3(dir, curv.principle[0]); + return; + } + + copy_v3_v3(dir, vertex_attr_ptr(v, ss->attrs.curvature_dir)); +} + +void SCULPT_curvature_begin(SculptSession *ss, struct PBVHNode *node, bool useAccurateSolver) +{ + if (BKE_pbvh_type(ss->pbvh) != PBVH_BMESH) { + /* Caching only happens for bmesh for now. */ + return; + } + + if (BKE_pbvh_curvature_update_get(node)) { + PBVHVertexIter vi; + + BKE_pbvh_curvature_update_set(node, false); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vi, PBVH_ITER_UNIQUE) { + SculptCurvatureData curv; + SCULPT_calc_principle_curvatures(ss, vi.vertex, &curv, useAccurateSolver); + + copy_v3_v3(vertex_attr_ptr(vi.vertex, ss->attrs.curvature_dir), curv.principle[0]); + } + BKE_pbvh_vertex_iter_end; + } +} diff --git a/source/blender/editors/sculpt_paint/sculpt_detail.cc b/source/blender/editors/sculpt_paint/sculpt_detail.cc index bcdf6000bfc..3d939059fbf 100644 --- a/source/blender/editors/sculpt_paint/sculpt_detail.cc +++ b/source/blender/editors/sculpt_paint/sculpt_detail.cc @@ -11,17 +11,14 @@ #include "BLI_blenlib.h" #include "BLI_math_matrix.h" #include "BLI_math_rotation.h" -#include "BLI_math_vector.hh" -#include "BLI_time.h" #include "BLT_translation.hh" #include "DNA_brush_types.h" #include "DNA_mesh_types.h" -#include "BKE_brush.hh" #include "BKE_context.hh" -#include "BKE_layer.hh" +#include "BKE_dyntopo.hh" #include "BKE_paint.hh" #include "BKE_pbvh_api.hh" #include "BKE_screen.hh" @@ -43,19 +40,20 @@ #include "RNA_access.hh" #include "RNA_define.hh" -#include "RNA_prototypes.h" -#include "UI_interface.hh" - -#include "CLG_log.h" +#include "BLI_time.h" #include #include +#include + +/* For detail flood fill developer mode */ +#include "../../blenkernel/intern/dyntopo_intern.hh" + +using blender::float3; + namespace blender::ed::sculpt_paint::dyntopo { - -static CLG_LogRef LOG = {"sculpt.detail"}; - /* -------------------------------------------------------------------- */ /** \name Internal Utilities * \{ */ @@ -66,16 +64,19 @@ struct SculptDetailRaycastData { float depth; float edge_length; - IsectRayPrecalc isect_precalc; + struct IsectRayPrecalc isect_precalc; + SculptSession *ss; }; static bool sculpt_and_constant_or_manual_detail_poll(bContext *C) { Object *ob = CTX_data_active_object(C); - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + // Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - return SCULPT_mode_poll(C) && ob->sculpt->bm && - (sd->flags & (SCULPT_DYNTOPO_DETAIL_CONSTANT | SCULPT_DYNTOPO_DETAIL_MANUAL)); + /*checking for constant/manual mode isn't necassary since we do this on the python side + in the ui scripts*/ + return SCULPT_mode_poll(C) && ob->sculpt->bm; /*&& + (sd->flags & (SCULPT_DYNTOPO_DETAIL_CONSTANT | SCULPT_DYNTOPO_DETAIL_MANUAL));*/ } static bool sculpt_and_dynamic_topology_poll(bContext *C) @@ -90,62 +91,353 @@ static bool sculpt_and_dynamic_topology_poll(bContext *C) /* -------------------------------------------------------------------- */ /** \name Detail Flood Fill * \{ */ +static int sculpt_detail_flood_fill_run(Scene *scene, + Object *ob, + ToolSettings *tool_settings, + Brush *brush, + wmOperator *op, + ViewContext *vc, + std::function lock_main_thread, + std::function unlock_main_thread, + std::function update_main_thread, + std::function should_stop) +{ + using namespace blender::bke::dyntopo; + + lock_main_thread(); + + /* NotForPR: used to debug dyntopo edge queue. */ + bool developer_mode = RNA_boolean_get(op->ptr, "developer"); + SculptSession *ss = ob->sculpt; + Sculpt *sd = tool_settings->sculpt; + + Vector nodes = blender::bke::pbvh::search_gather(ss->pbvh, {}); + + if (nodes.is_empty()) { + unlock_main_thread(); + return OPERATOR_CANCELLED; + } + + dyntopo::apply_settings(scene, ss, sd, brush); + float detail_range = DYNTOPO_DETAIL_RANGE; + + /* Update topology size. */ + float object_space_constant_detail = 1.0f / (ss->cached_dyntopo.constant_detail * + mat4_to_scale(ob->object_to_world().ptr())); + blender::bke::dyntopo::detail_size_set(ss->pbvh, object_space_constant_detail, detail_range); + BKE_pbvh_set_bm_log(ss->pbvh, ss->bm_log); + + DyntopoMaskCB mask_cb; + void *mask_cb_data; + + SCULPT_dyntopo_automasking_init(ss, sd, nullptr, ob, &mask_cb, &mask_cb_data); + BM_log_entry_check_customdata(ss->bm, ss->bm_log); + + PBVHTopologyUpdateMode mode = PBVHTopologyUpdateMode(0); + if (ss->cached_dyntopo.flag & DYNTOPO_SUBDIVIDE) { + mode |= PBVH_Subdivide; + } + if (ss->cached_dyntopo.flag & DYNTOPO_COLLAPSE) { + mode |= PBVH_Collapse; + } + if (ss->cached_dyntopo.flag & DYNTOPO_CLEANUP) { + mode |= PBVH_Cleanup; + } + + double time = BLI_time_now_seconds(); + + unlock_main_thread(); + + float radius; + float3 center; + bool emulate_brush = RNA_boolean_get(op->ptr, "emulate_brush") && developer_mode && vc; + + if (emulate_brush && vc) { + // ViewContext + + if (tool_settings->unified_paint_settings.average_stroke_counter) { + center = float3(tool_settings->unified_paint_settings.average_stroke_accum) / + float(tool_settings->unified_paint_settings.average_stroke_counter); + } + else { + zero_v3(center); + } + + radius = sculpt_calc_radius(vc, brush, vc->scene, center); + printf("radius: %.5f", radius); + } + + double last_flush = BLI_time_now_seconds(); + + int repeat = ss->cached_dyntopo.repeat; + for (int i = 0; i < 1 + repeat; i++) { + lock_main_thread(); + + BrushNoRadius null_brush_tester; + BrushSphere sphere_brush_tester(center, radius); + BrushTester *tester; + + nodes = blender::bke::pbvh::search_gather(ss->pbvh, {}); + + if (!emulate_brush) { + tester = &null_brush_tester; + } + else { + tester = &sphere_brush_tester; + +#if 0 + SculptSearchSphereData data{}; + data.sd = sd; + data.radius_squared = radius*radius; + data.original = false; + data.center = center; + + nodes = blender::bke::pbvh::search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data); +#endif + } + + for (int j = 0; j < nodes.size(); j++) { + BKE_pbvh_node_mark_topology_update(nodes[j]); + } + + EdgeQueueContext remesher( + tester, ob, ss->pbvh, mode, false, float3(0.0f, 0.0f, 1.0f), false, mask_cb, mask_cb_data); + + remesher.surface_smooth_fac = 0.25; + remesher.start(); + + float quality = ss->cached_dyntopo.quality; + float time_limit = (min_ff(1.0f * (100.0f - quality + 2500.0f * quality), 2500.0f)) * 0.001f; + float quality_time = BLI_time_now_seconds(); + + printf("Remesh\n"); + + while (!remesher.done() && BLI_time_now_seconds() - quality_time < time_limit) { + remesher.step(); + + if (developer_mode && + (remesher.was_flushed() || (BLI_time_now_seconds() - last_flush > 0.5))) + { + DEG_id_tag_update(&ob->id, ID_RECALC_SHADING); + unlock_main_thread(); + update_main_thread(); + BLI_time_sleep_ms(1); + lock_main_thread(); + + last_flush = BLI_time_now_seconds(); + } + + if (should_stop()) { + break; + } + } + + remesher.finish(); + + /* Push a new subentry. */ + BM_log_entry_add_delta_set(ss->bm, ss->bm_log); + blender::bke::dyntopo::after_stroke(ss->pbvh, true); + + unlock_main_thread(); + } + + lock_main_thread(); + + time = (BLI_time_now_seconds() - time) * 1000.0; + printf(" Time: %.3fms\n", float(time)); + + SCULPT_dyntopo_automasking_end(mask_cb_data); + + after_stroke(ss->pbvh, true); + DEG_id_tag_update(&ob->id, ID_RECALC_SHADING); + + unlock_main_thread(); + + return OPERATOR_FINISHED; +} static int sculpt_detail_flood_fill_exec(bContext *C, wmOperator *op) { - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - const View3D *v3d = CTX_wm_view3d(C); - const Base *base = CTX_data_active_base(C); - if (!BKE_base_is_visible(v3d, base)) { - return OPERATOR_CANCELLED; - } - - Vector nodes = bke::pbvh::search_gather(ss->pbvh, {}); - - if (nodes.is_empty()) { - return OPERATOR_CANCELLED; - } - - for (PBVHNode *node : nodes) { - BKE_pbvh_node_mark_topology_update(node); - } - /* Get the bounding box, its center and size. */ - const Bounds bounds = BKE_pbvh_bounding_box(ob->sculpt->pbvh); - const float3 center = math::midpoint(bounds.min, bounds.max); - const float3 dim = bounds.max - bounds.min; - const float size = math::reduce_max(dim); - - /* Update topology size. */ - float object_space_constant_detail = 1.0f / (sd->constant_detail * - mat4_to_scale(ob->object_to_world().ptr())); - BKE_pbvh_bmesh_detail_size_set(ss->pbvh, object_space_constant_detail); + BKE_sculpt_update_object_for_edit(depsgraph, ob, false); undo::push_begin(ob, op); undo::push_node(ob, nullptr, undo::Type::Position); - const double start_time = BLI_time_now_seconds(); + SculptSession *ss = ob->sculpt; + BMesh *bm = ss->bm; + BMIter iter; + BMVert *v; - while (bke::pbvh::bmesh_update_topology( - ss->pbvh, PBVH_Collapse | PBVH_Subdivide, center, nullptr, size, false, false)) - { - for (PBVHNode *node : nodes) { - BKE_pbvh_node_mark_topology_update(node); - } + BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { + BM_log_vert_if_modified(bm, ss->bm_log, v); } - CLOG_INFO(&LOG, 2, "Detail flood fill took %f seconds.", BLI_time_now_seconds() - start_time); + double time = BLI_time_now_seconds(); + auto should_stop = [&time]() { return BLI_time_now_seconds() - time > 1.0f; }; + + int ret = sculpt_detail_flood_fill_run( + CTX_data_scene(C), + CTX_data_active_object(C), + CTX_data_tool_settings(C), + BKE_paint_brush(&sd->paint), + op, + nullptr, + []() {}, + []() {}, + []() {}, + should_stop); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); undo::push_end(ob); - /* Force rebuild of PBVH for better BB placement. */ - SCULPT_pbvh_clear(ob); - /* Redraw. */ - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + return ret; +} - return OPERATOR_FINISHED; +struct FloodFillJob { + wmJob *job; + Object *ob; + Depsgraph *depsgraph; + Scene *scene; + wmOperator *op; + bContext *C; + Brush *brush; + ToolSettings *tool_settings; + ViewContext vc; +}; + +static FloodFillJob flood_fill_job; + +static void start_fill_job(void * /*custom_data*/, wmJobWorkerStatus *status) +{ + printf("Start detail fill job.\n"); + + auto lock_main_thread = [&]() { WM_job_main_thread_lock_acquire(flood_fill_job.job); }; + auto unlock_main_thread = [&]() { WM_job_main_thread_lock_release(flood_fill_job.job); }; + auto update_main_thread = [&]() { status->do_update = true; }; + auto should_stop = [&]() { return status->stop; }; + + while (true) { + if (status->stop) { + break; + } + + if (sculpt_detail_flood_fill_run(flood_fill_job.scene, + flood_fill_job.ob, + flood_fill_job.tool_settings, + flood_fill_job.brush, + flood_fill_job.op, + &flood_fill_job.vc, + lock_main_thread, + unlock_main_thread, + update_main_thread, + should_stop) == OPERATOR_CANCELLED) + { + break; + } + + status->do_update = true; + BLI_time_sleep_ms(15); + } + + printf("\nJob finished\n\n"); +} + +static void end_fill_job(void *) +{ + undo::push_end(flood_fill_job.ob); + + printf("End fill job\n"); +} + +static void flood_fill_free(void *) +{ + printf("%s: detail flood fill free.\n", __func__); +} + +int sculpt_detail_flood_fill_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/) +{ + ED_workspace_status_text(C, TIP_("")); + + if (RNA_boolean_get(op->ptr, "interactive")) { + Object *ob = CTX_data_active_object(C); + + BKE_sculpt_update_object_for_edit(CTX_data_ensure_evaluated_depsgraph(C), ob, false); + + undo::push_begin(ob, op); + undo::push_node(ob, nullptr, undo::Type::Position); + + SculptSession *ss = ob->sculpt; + BMesh *bm = ss->bm; + BMIter iter; + BMVert *v; + + BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { + BM_log_vert_if_modified(bm, ss->bm_log, v); + } + + flood_fill_job.tool_settings = CTX_data_tool_settings(C); + flood_fill_job.brush = BKE_paint_brush(&flood_fill_job.tool_settings->sculpt->paint); + flood_fill_job.ob = CTX_data_active_object(C); + flood_fill_job.op = op; + flood_fill_job.C = C; + flood_fill_job.scene = CTX_data_scene(C); + flood_fill_job.depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + flood_fill_job.vc = ED_view3d_viewcontext_init(C, flood_fill_job.depsgraph); + + if (!flood_fill_job.vc.rv3d) { + LISTBASE_FOREACH (ScrArea *, area, &CTX_wm_screen(C)->areabase) { + if (area->spacetype == SPACE_VIEW3D) { + LISTBASE_FOREACH (ARegion *, region, &area->regionbase) { + if (region->regiontype == RGN_TYPE_WINDOW) { + flood_fill_job.vc.region = region; + flood_fill_job.vc.rv3d = static_cast(region->regiondata); + break; + } + } + break; + } + + if (flood_fill_job.vc.rv3d) { + break; + } + } + } + flood_fill_job.job = WM_jobs_get(CTX_wm_manager(C), + CTX_wm_window(C), + static_cast(&flood_fill_job), + "Dyntopo Flood Fill", + WM_JOB_PROGRESS, + WM_JOB_TYPE_ANY); + + WM_jobs_callbacks(flood_fill_job.job, start_fill_job, nullptr, nullptr, end_fill_job); + WM_jobs_timer(flood_fill_job.job, 0.5, NC_OBJECT | ND_DRAW, NC_OBJECT | ND_DRAW); + WM_jobs_customdata_set(flood_fill_job.job, &flood_fill_job, flood_fill_free); + + WM_jobs_start(CTX_wm_manager(C), flood_fill_job.job); + + WM_event_add_modal_handler(C, op); + return OPERATOR_RUNNING_MODAL; + } + else { + sculpt_detail_flood_fill_exec(C, op); + return OPERATOR_FINISHED; + } +} + +static int sculpt_sample_flood_fill_modal(bContext *C, wmOperator * /*op*/, const wmEvent *event) +{ + switch (event->type) { + case EVT_ESCKEY: + case EVT_RETKEY: + WM_jobs_kill(CTX_wm_manager(C), &flood_fill_job, start_fill_job); + return OPERATOR_FINISHED; + } + + return OPERATOR_RUNNING_MODAL; } void SCULPT_OT_detail_flood_fill(wmOperatorType *ot) @@ -158,8 +450,17 @@ void SCULPT_OT_detail_flood_fill(wmOperatorType *ot) /* API callbacks. */ ot->exec = sculpt_detail_flood_fill_exec; ot->poll = sculpt_and_constant_or_manual_detail_poll; + ot->invoke = sculpt_detail_flood_fill_invoke; + ot->modal = sculpt_sample_flood_fill_modal; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean(ot->srna, "interactive", true, "Interactive", "Interactive mode"); + + /* NotForPR: used to debug dyntopo edge queue. */ + RNA_def_boolean(ot->srna, "developer", false, "Developer", "Developer mode"); + RNA_def_boolean( + ot->srna, "emulate_brush", false, "Emulate Brush", "Use last brush position and radius"); } /** \} */ @@ -210,14 +511,18 @@ static void sample_detail_voxel(bContext *C, ViewContext *vc, const int mval[2]) } } -static void sculpt_raycast_detail_cb(PBVHNode &node, SculptDetailRaycastData &srd, float *tmin) +static void sculpt_raycast_detail_cb(PBVHNode *node, SculptDetailRaycastData *srd, float *tmin) { - if (BKE_pbvh_node_get_tmin(&node) < *tmin) { - if (bke::pbvh::bmesh_node_raycast_detail( - &node, srd.ray_start, &srd.isect_precalc, &srd.depth, &srd.edge_length)) + if (BKE_pbvh_node_get_tmin(node) < *tmin) { + if (BKE_pbvh_bmesh_node_raycast_detail(srd->ss->pbvh, + node, + srd->ray_start, + &srd->isect_precalc, + &srd->depth, + &srd->edge_length)) { - srd.hit = true; - *tmin = srd.depth; + srd->hit = true; + *tmin = srd->depth; } } } @@ -236,6 +541,7 @@ static void sample_detail_dyntopo(bContext *C, ViewContext *vc, const int mval[2 SculptDetailRaycastData srd; srd.hit = false; + srd.ss = ob->sculpt; srd.ray_start = ray_start; srd.depth = depth; srd.edge_length = 0.0f; @@ -243,14 +549,20 @@ static void sample_detail_dyntopo(bContext *C, ViewContext *vc, const int mval[2 bke::pbvh::raycast( ob->sculpt->pbvh, - [&](PBVHNode &node, float *tmin) { sculpt_raycast_detail_cb(node, srd, tmin); }, + [&](PBVHNode &node, float *tmin) { sculpt_raycast_detail_cb(&node, &srd, tmin); }, ray_start, ray_normal, - false); + false, + srd.ss->stroke_id); if (srd.hit && srd.edge_length > 0.0f) { + DynTopoSettings *dyntopo = brush->dyntopo.inherit & DYNTOPO_INHERIT_CONSTANT_DETAIL ? + &sd->dyntopo : + &brush->dyntopo; + /* Convert edge length to world space detail resolution. */ - sd->constant_detail = 1 / (srd.edge_length * mat4_to_scale(ob->object_to_world().ptr())); + dyntopo->constant_detail = 1.0f / + (srd.edge_length * mat4_to_scale(ob->object_to_world().ptr())); } } @@ -271,7 +583,8 @@ static int sample_detail(bContext *C, const int event_xy[2], int mode) CTX_wm_region_set(C, region); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ViewContext vc; + vc = ED_view3d_viewcontext_init(C, depsgraph); Object *ob = vc.obact; if (ob == nullptr) { @@ -283,12 +596,6 @@ static int sample_detail(bContext *C, const int event_xy[2], int mode) return OPERATOR_CANCELLED; } - const View3D *v3d = CTX_wm_view3d(C); - const Base *base = CTX_data_active_base(C); - if (!BKE_base_is_visible(v3d, base)) { - return OPERATOR_CANCELLED; - } - const int mval[2] = { event_xy[0] - region->winrct.xmin, event_xy[1] - region->winrct.ymin, @@ -331,7 +638,7 @@ static int sculpt_sample_detail_size_exec(bContext *C, wmOperator *op) static int sculpt_sample_detail_size_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/) { - ED_workspace_status_text(C, IFACE_("Click on the mesh to set the detail")); + ED_workspace_status_text(C, TIP_("Click on the mesh to set the detail")); WM_cursor_modal_set(CTX_wm_window(C), WM_CURSOR_EYEDROPPER); WM_event_add_modal_handler(C, op); return OPERATOR_RUNNING_MODAL; @@ -400,6 +707,77 @@ void SCULPT_OT_sample_detail_size(wmOperatorType *ot) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Dynamic-topology detail size + * + * Currently, there are two operators editing the detail size: + * - #SCULPT_OT_set_detail_size uses radial control for all methods + * - #SCULPT_OT_dyntopo_detail_size_edit shows a triangle grid representation of the detail + * resolution (for constant detail method, + * falls back to radial control for the remaining methods). + * \{ */ + +static void sculpt_detail_size_set_radial_control(bContext *C) +{ + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + PointerRNA props_ptr; + wmOperatorType *ot = WM_operatortype_find("WM_OT_radial_control", true); + + WM_operator_properties_create_ptr(&props_ptr, ot); + + int mode = brush->dyntopo.inherit & DYNTOPO_INHERIT_MODE ? sd->dyntopo.mode : + brush->dyntopo.mode; + std::string base = "tool_settings.sculpt"; + if (!(brush->dyntopo.inherit & DYNTOPO_INHERIT_MODE)) { + base += ".brush"; + } + + base += ".dyntopo"; + + if (ELEM(mode, DYNTOPO_DETAIL_MANUAL, DYNTOPO_DETAIL_CONSTANT)) { + base += ".constant_detail"; + RNA_string_set(&props_ptr, "data_path_primary", base.c_str()); + } + else if (mode == DYNTOPO_DETAIL_BRUSH) { + base += ".detail_percent"; + RNA_string_set(&props_ptr, "data_path_primary", base.c_str()); + } + else { /* Relative mode. */ + base += ".detail_size"; + RNA_string_set(&props_ptr, "data_path_primary", base.c_str()); + } + + WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr, nullptr); + + WM_operator_properties_free(&props_ptr); +} + +static int sculpt_set_detail_size_exec(bContext *C, wmOperator * /*op*/) +{ + sculpt_detail_size_set_radial_control(C); + + return OPERATOR_FINISHED; +} + +void SCULPT_OT_set_detail_size(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Set Detail Size"; + ot->idname = "SCULPT_OT_set_detail_size"; + ot->description = + "Set the mesh detail (either relative or constant one, depending on current dyntopo mode)"; + + /* API callbacks. */ + ot->exec = sculpt_set_detail_size_exec; + ot->poll = sculpt_and_dynamic_topology_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Dyntopo Detail Size Edit Operator * \{ */ @@ -408,18 +786,10 @@ void SCULPT_OT_sample_detail_size(wmOperatorType *ot) #define DETAIL_SIZE_DELTA_SPEED 0.08f #define DETAIL_SIZE_DELTA_ACCURATE_SPEED 0.004f -enum eDyntopoDetailingMode { - DETAILING_MODE_RESOLUTION = 0, - DETAILING_MODE_BRUSH_PERCENT = 1, - DETAILING_MODE_DETAIL_SIZE = 2 -}; - struct DyntopoDetailSizeEditCustomData { void *draw_handle; Object *active_object; - eDyntopoDetailingMode mode; - float init_mval[2]; float accurate_mval[2]; @@ -428,19 +798,12 @@ struct DyntopoDetailSizeEditCustomData { bool accurate_mode; bool sample_mode; - /* The values stored here vary based on the detailing mode. */ - float init_value; - float accurate_value; - float current_value; - + float init_detail_size; + float accurate_detail_size; + float detail_size; + float detail_range; float radius; - float brush_radius; - float pixel_radius; - - float min_value; - float max_value; - float preview_tri[3][3]; float gizmo_mat[4][4]; }; @@ -452,26 +815,16 @@ static void dyntopo_detail_size_parallel_lines_draw(uint pos3d, bool flip, const float angle) { - float object_space_constant_detail; - if (cd->mode == DETAILING_MODE_RESOLUTION) { - object_space_constant_detail = detail_size::constant_to_detail_size(cd->current_value, - cd->active_object); - } - else if (cd->mode == DETAILING_MODE_BRUSH_PERCENT) { - object_space_constant_detail = detail_size::brush_to_detail_size(cd->current_value, - cd->brush_radius); - } - else { - object_space_constant_detail = detail_size::relative_to_detail_size( - cd->current_value, cd->brush_radius, cd->pixel_radius, U.pixelsize); - } + float object_space_constant_detail = 1.0f / + (cd->detail_size * + mat4_to_scale(cd->active_object->object_to_world().ptr())); /* The constant detail represents the maximum edge length allowed before subdividing it. If the - * triangle grid preview is created with this value it will represent an ideal mesh density where - * all edges have the exact maximum length, which never happens in practice. As the minimum edge - * length for dyntopo is 0.4 * max_edge_length, this adjust the detail size to the average - * between max and min edge length so the preview is more accurate. */ - object_space_constant_detail *= 0.7f; + * triangle grid preview is created with this value it will represent an ideal mesh density + * where all edges have the exact maximum length, which never happens in practice. As the + * minimum edge length for dyntopo is 0.4 * max_edge_length, this adjust the detail size to the + * average between max and min edge length so the preview is more accurate. */ + object_space_constant_detail *= 1.0f - DYNTOPO_DETAIL_RANGE * 0.5f; const float total_len = len_v3v3(cd->preview_tri[0], cd->preview_tri[1]); const int tot_lines = int(total_len / object_space_constant_detail) + 1; @@ -560,26 +913,6 @@ static void dyntopo_detail_size_edit_cancel(bContext *C, wmOperator *op) ss->draw_faded_cursor = false; MEM_freeN(op->customdata); ED_workspace_status_text(C, nullptr); - - ScrArea *area = CTX_wm_area(C); - ED_area_status_text(area, nullptr); -} - -static void dyntopo_detail_size_bounds(DyntopoDetailSizeEditCustomData *cd) -{ - /* TODO: Get range from RNA for these values? */ - if (cd->mode == DETAILING_MODE_RESOLUTION) { - cd->min_value = 1.0f; - cd->max_value = 500.0f; - } - else if (cd->mode == DETAILING_MODE_BRUSH_PERCENT) { - cd->min_value = 0.5f; - cd->max_value = 100.0f; - } - else { - cd->min_value = 0.5f; - cd->max_value = 40.0f; - } } static void dyntopo_detail_size_sample_from_surface(Object *ob, @@ -603,19 +936,7 @@ static void dyntopo_detail_size_sample_from_surface(Object *ob, /* Use 0.7 as the average of min and max dyntopo edge length. */ const float detail_size = 0.7f / (avg_edge_len * mat4_to_scale(cd->active_object->object_to_world().ptr())); - float sampled_value; - if (cd->mode == DETAILING_MODE_RESOLUTION) { - sampled_value = detail_size; - } - else if (cd->mode == DETAILING_MODE_BRUSH_PERCENT) { - sampled_value = detail_size::constant_to_brush_detail( - detail_size, cd->brush_radius, cd->active_object); - } - else { - sampled_value = detail_size::constant_to_relative_detail( - detail_size, cd->brush_radius, cd->pixel_radius, U.pixelsize, cd->active_object); - } - cd->current_value = clamp_f(sampled_value, cd->min_value, cd->max_value); + cd->detail_size = clamp_f(detail_size, 1.0f, 500.0f); } } @@ -625,58 +946,27 @@ static void dyntopo_detail_size_update_from_mouse_delta(DyntopoDetailSizeEditCus const float mval[2] = {float(event->mval[0]), float(event->mval[1])}; float detail_size_delta; - float invert = cd->mode == DETAILING_MODE_RESOLUTION ? 1.0f : -1.0f; if (cd->accurate_mode) { detail_size_delta = mval[0] - cd->accurate_mval[0]; - cd->current_value = cd->accurate_value + - detail_size_delta * DETAIL_SIZE_DELTA_ACCURATE_SPEED * invert; + cd->detail_size = cd->accurate_detail_size + + detail_size_delta * DETAIL_SIZE_DELTA_ACCURATE_SPEED; } else { detail_size_delta = mval[0] - cd->init_mval[0]; - cd->current_value = cd->init_value + detail_size_delta * DETAIL_SIZE_DELTA_SPEED * invert; + cd->detail_size = cd->init_detail_size + detail_size_delta * DETAIL_SIZE_DELTA_SPEED; } if (event->type == EVT_LEFTSHIFTKEY && event->val == KM_PRESS) { cd->accurate_mode = true; copy_v2_v2(cd->accurate_mval, mval); - cd->accurate_value = cd->current_value; + cd->accurate_detail_size = cd->detail_size; } if (event->type == EVT_LEFTSHIFTKEY && event->val == KM_RELEASE) { cd->accurate_mode = false; - cd->accurate_value = 0.0f; + cd->accurate_detail_size = 0.0f; } - cd->current_value = clamp_f(cd->current_value, cd->min_value, cd->max_value); -} - -static void dyntopo_detail_size_update_header(bContext *C, - const DyntopoDetailSizeEditCustomData *cd) -{ - Scene *scene = CTX_data_scene(C); - - Sculpt *sd = scene->toolsettings->sculpt; - PointerRNA sculpt_ptr = RNA_pointer_create(&scene->id, &RNA_Sculpt, sd); - - char msg[UI_MAX_DRAW_STR]; - const char *format_string; - const char *property_name; - if (cd->mode == DETAILING_MODE_RESOLUTION) { - property_name = "constant_detail_resolution"; - format_string = "%s: %0.4f"; - } - else if (cd->mode == DETAILING_MODE_BRUSH_PERCENT) { - property_name = "detail_percent"; - format_string = "%s: %3.1f%%"; - } - else { - property_name = "detail_size"; - format_string = "%s: %0.4f"; - } - const PropertyRNA *prop = RNA_struct_find_property(&sculpt_ptr, property_name); - const char *ui_name = RNA_property_ui_name(prop); - SNPRINTF(msg, format_string, ui_name, cd->current_value); - ScrArea *area = CTX_wm_area(C); - ED_area_status_text(area, msg); + cd->detail_size = clamp_f(cd->detail_size, 1.0f, 500.0f); } static int dyntopo_detail_size_edit_modal(bContext *C, wmOperator *op, const wmEvent *event) @@ -687,6 +977,7 @@ static int dyntopo_detail_size_edit_modal(bContext *C, wmOperator *op, const wmE DyntopoDetailSizeEditCustomData *cd = static_cast( op->customdata); Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); /* Cancel modal operator */ if ((event->type == EVT_ESCKEY && event->val == KM_PRESS) || @@ -703,23 +994,17 @@ static int dyntopo_detail_size_edit_modal(bContext *C, wmOperator *op, const wmE (event->type == EVT_PADENTER && event->val == KM_PRESS)) { ED_region_draw_cb_exit(region->type, cd->draw_handle); - if (cd->mode == DETAILING_MODE_RESOLUTION) { - sd->constant_detail = cd->current_value; - } - else if (cd->mode == DETAILING_MODE_BRUSH_PERCENT) { - sd->detail_percent = cd->current_value; + + if (brush->dyntopo.inherit & DYNTOPO_INHERIT_CONSTANT_DETAIL) { + sd->dyntopo.constant_detail = cd->detail_size; } else { - sd->detail_size = cd->current_value; + brush->dyntopo.constant_detail = cd->detail_size; } - ss->draw_faded_cursor = false; MEM_freeN(op->customdata); ED_region_tag_redraw(region); ED_workspace_status_text(C, nullptr); - - ScrArea *area = CTX_wm_area(C); - ED_area_status_text(area, nullptr); return OPERATOR_FINISHED; } @@ -741,69 +1026,48 @@ static int dyntopo_detail_size_edit_modal(bContext *C, wmOperator *op, const wmE } /* Regular mode, changes the detail size by moving the cursor. */ dyntopo_detail_size_update_from_mouse_delta(cd, event); - dyntopo_detail_size_update_header(C, cd); return OPERATOR_RUNNING_MODAL; } -static float dyntopo_detail_size_initial_value(const Sculpt *sd, const eDyntopoDetailingMode mode) -{ - if (mode == DETAILING_MODE_RESOLUTION) { - return sd->constant_detail; - } - else if (mode == DETAILING_MODE_BRUSH_PERCENT) { - return sd->detail_percent; - } - else { - return sd->detail_size; - } -} - static int dyntopo_detail_size_edit_invoke(bContext *C, wmOperator *op, const wmEvent *event) { - const ToolSettings *tool_settings = CTX_data_tool_settings(C); - Sculpt *sd = tool_settings->sculpt; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + /* Fallback to radial control for modes other than SCULPT_DYNTOPO_DETAIL_CONSTANT [same as in + * SCULPT_OT_set_detail_size]. */ + int mode = !brush || brush->dyntopo.inherit & DYNTOPO_INHERIT_MODE ? sd->dyntopo.mode : + brush->dyntopo.mode; + if (!ELEM(mode, DYNTOPO_DETAIL_MANUAL, DYNTOPO_DETAIL_CONSTANT)) { + sculpt_detail_size_set_radial_control(C); + + return OPERATOR_FINISHED; + } + + /* Special method for SCULPT_DYNTOPO_DETAIL_CONSTANT. */ ARegion *region = CTX_wm_region(C); Object *active_object = CTX_data_active_object(C); - Brush *brush = BKE_paint_brush(&sd->paint); DyntopoDetailSizeEditCustomData *cd = MEM_cnew(__func__); - /* Initial operator Custom Data setup. */ + int constant_detail = brush->dyntopo.inherit & DYNTOPO_INHERIT_CONSTANT_DETAIL ? + sd->dyntopo.constant_detail : + brush->dyntopo.constant_detail; + cd->draw_handle = ED_region_draw_cb_activate( region->type, dyntopo_detail_size_edit_draw, cd, REGION_DRAW_POST_VIEW); cd->active_object = active_object; cd->init_mval[0] = event->mval[0]; cd->init_mval[1] = event->mval[1]; - if (sd->flags & (SCULPT_DYNTOPO_DETAIL_CONSTANT | SCULPT_DYNTOPO_DETAIL_MANUAL)) { - cd->mode = DETAILING_MODE_RESOLUTION; - } - else if (sd->flags & SCULPT_DYNTOPO_DETAIL_BRUSH) { - cd->mode = DETAILING_MODE_BRUSH_PERCENT; - } - else { - cd->mode = DETAILING_MODE_DETAIL_SIZE; - } - - const float initial_detail_size = dyntopo_detail_size_initial_value(sd, cd->mode); - cd->current_value = initial_detail_size; - cd->init_value = initial_detail_size; + cd->detail_size = constant_detail; + cd->init_detail_size = constant_detail; copy_v4_v4(cd->outline_col, brush->add_col); op->customdata = cd; SculptSession *ss = active_object->sculpt; - dyntopo_detail_size_bounds(cd); cd->radius = ss->cursor_radius; - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); - - const Scene *scene = CTX_data_scene(C); - cd->brush_radius = blender::ed::sculpt_paint::sculpt_calc_radius( - &vc, brush, scene, ss->cursor_location); - cd->pixel_radius = BKE_brush_size_get(scene, brush); - /* Generates the matrix to position the gizmo in the surface of the mesh using the same * location and orientation as the brush cursor. */ float cursor_trans[4][4], cursor_rot[4][4]; @@ -840,12 +1104,10 @@ static int dyntopo_detail_size_edit_invoke(bContext *C, wmOperator *op, const wm ss->draw_faded_cursor = true; - const char *status_str = IFACE_( + const char *status_str = TIP_( "Move the mouse to change the dyntopo detail size. LMB: confirm size, ESC/RMB: cancel, " "SHIFT: precision mode, CTRL: sample detail size"); - ED_workspace_status_text(C, status_str); - dyntopo_detail_size_update_header(C, cd); return OPERATOR_RUNNING_MODAL; } @@ -866,47 +1128,5 @@ void SCULPT_OT_dyntopo_detail_size_edit(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } -} // namespace blender::ed::sculpt_paint::dyntopo - -namespace blender::ed::sculpt_paint::dyntopo::detail_size { -float constant_to_detail_size(const float constant_detail, const Object *ob) -{ - return 1.0f / (constant_detail * mat4_to_scale(ob->object_to_world().ptr())); -} - -float brush_to_detail_size(const float brush_percent, const float brush_radius) -{ - return brush_radius * brush_percent / 100.0f; -} - -float relative_to_detail_size(const float relative_detail, - const float brush_radius, - const float pixel_radius, - const float pixel_size) -{ - return (brush_radius / pixel_radius) * (relative_detail * pixel_size) / RELATIVE_SCALE_FACTOR; -} - -float constant_to_brush_detail(const float constant_detail, - const float brush_radius, - const Object *ob) -{ - const float object_scale = mat4_to_scale(ob->object_to_world().ptr()); - - return 100.0f / (constant_detail * brush_radius * object_scale); -} - -float constant_to_relative_detail(const float constant_detail, - const float brush_radius, - const float pixel_radius, - const float pixel_size, - const Object *ob) -{ - const float object_scale = mat4_to_scale(ob->object_to_world().ptr()); - - return (pixel_radius / brush_radius) * (RELATIVE_SCALE_FACTOR / pixel_size) * - (1.0f / (constant_detail * object_scale)); -} -} // namespace blender::ed::sculpt_paint::dyntopo::detail_size - /** \} */ +} // namespace blender::ed::sculpt_paint::dyntopo diff --git a/source/blender/editors/sculpt_paint/sculpt_displacement.c b/source/blender/editors/sculpt_paint/sculpt_displacement.c new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_displacement.c @@ -0,0 +1 @@ + diff --git a/source/blender/editors/sculpt_paint/sculpt_displacement.h b/source/blender/editors/sculpt_paint/sculpt_displacement.h new file mode 100644 index 00000000000..ea38e1be42e --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_displacement.h @@ -0,0 +1,5 @@ +#pragma once + +typedef struct SculptDisplacer { + +} SculptDisplacer; diff --git a/source/blender/editors/sculpt_paint/sculpt_dyntopo.cc b/source/blender/editors/sculpt_paint/sculpt_dyntopo.cc index 6b511aefdbb..c2fef3627eb 100644 --- a/source/blender/editors/sculpt_paint/sculpt_dyntopo.cc +++ b/source/blender/editors/sculpt_paint/sculpt_dyntopo.cc @@ -11,13 +11,21 @@ #include "MEM_guardedalloc.h" +#include "BLI_compiler_attrs.h" +#include "BLI_math_vector.h" + #include "BLT_translation.hh" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" #include "DNA_modifier_types.h" +#include "BKE_brush.hh" +#include "BKE_colortools.hh" #include "BKE_context.hh" #include "BKE_global.hh" #include "BKE_mesh.hh" +#include "BKE_mesh_mapping.hh" #include "BKE_modifier.hh" #include "BKE_object.hh" #include "BKE_paint.hh" @@ -40,8 +48,74 @@ #include "UI_resources.hh" #include "bmesh.hh" +#include "bmesh_idmap.hh" +#include "bmesh_log.hh" #include "bmesh_tools.hh" +#include +#include + +using namespace blender::ed::sculpt_paint; +using namespace blender; + +BMesh *SCULPT_dyntopo_empty_bmesh() +{ + return BKE_sculptsession_empty_bmesh_create(); +} + +void SCULPT_update_all_valence_boundary(Object *ob) +{ + SculptSession *ss = ob->sculpt; + + /* Do bmesh seperately to avoid needing the PBVH, which we might not + * have inside the undo code. + */ + + if (ss->bm) { + SCULPT_vertex_random_access_ensure(ss); + BMIter iter; + BMVert *v; + + int cd_flag = CustomData_get_offset_named( + &ss->bm->vdata, CD_PROP_INT8, SCULPT_ATTRIBUTE_NAME(flags)); + int cd_boundary = CustomData_get_offset_named( + &ss->bm->vdata, CD_PROP_INT32, SCULPT_ATTRIBUTE_NAME(boundary_flags)); + int cd_valence = CustomData_get_offset_named( + &ss->bm->vdata, CD_PROP_INT32, SCULPT_ATTRIBUTE_NAME(valence)); + + BLI_assert(cd_flag != -1 && cd_boundary != -1 && cd_valence != -1); + + BM_ITER_MESH (v, &iter, ss->bm, BM_VERTS_OF_MESH) { + *BM_ELEM_CD_PTR(v, cd_flag) = SCULPTFLAG_NEED_TRIANGULATE; + BM_ELEM_CD_SET_INT(v, cd_valence, BM_vert_edge_count(v)); + *BM_ELEM_CD_PTR(v, cd_boundary) |= SCULPT_BOUNDARY_NEEDS_UPDATE | + SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE; + + /* Update boundary if we have a pbvh. */ + if (ss->pbvh) { + PBVHVertRef vertex = {reinterpret_cast(v)}; + SCULPT_vertex_is_boundary(ss, vertex, SCULPT_BOUNDARY_ALL); + } + } + + return; + } + if (!ss->pbvh) { + return; + } + + int verts_count = SCULPT_vertex_count_get(ss); + for (int i = 0; i < verts_count; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + blender::bke::paint::vertex_attr_set( + vertex, ss->attrs.flags, SCULPTFLAG_NEED_VALENCE | SCULPTFLAG_NEED_TRIANGULATE); + BKE_sculpt_boundary_flag_update(ss, vertex); + SCULPT_vertex_valence_get(ss, vertex); + SCULPT_vertex_is_boundary(ss, vertex, SCULPT_BOUNDARY_ALL); + } +} + void SCULPT_pbvh_clear(Object *ob) { using namespace blender; @@ -60,62 +134,130 @@ void SCULPT_pbvh_clear(Object *ob) } namespace blender::ed::sculpt_paint::dyntopo { - -void triangulate(BMesh *bm) +// XXX is this needed? +static void customdata_strip_templayers(CustomData *cdata, int totelem) { - if (bm->totloop != bm->totface * 3) { - BM_mesh_triangulate(bm, - MOD_TRIANGULATE_QUAD_BEAUTY, - MOD_TRIANGULATE_NGON_EARCLIP, - 4, - false, - nullptr, - nullptr, - nullptr); + for (int i = 0; i < cdata->totlayer; i++) { + CustomDataLayer *layer = cdata->layers + i; + + if (layer->flag & CD_FLAG_TEMPORARY) { + CustomData_free_layer(cdata, eCustomDataType(layer->type), totelem, i); + i--; + } } } void enable_ex(Main *bmain, Depsgraph *depsgraph, Object *ob) { SculptSession *ss = ob->sculpt; - Mesh *mesh = static_cast(ob->data); - const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh); + Mesh *mesh = BKE_object_get_original_mesh(ob); - SCULPT_pbvh_clear(ob); + customdata_strip_templayers(&mesh->vert_data, mesh->verts_num); + customdata_strip_templayers(&mesh->face_data, mesh->faces_num); + + if (!ss->vert_to_face_map.is_empty()) { + ss->vert_to_face_map = {}; + ss->edge_to_face_map = {}; + ss->edge_to_face_indices = {}; + ss->edge_to_face_offsets = {}; + ss->vert_to_edge_map = {}; + } + + if (!ss->bm || !ss->pbvh || BKE_pbvh_type(ss->pbvh) != PBVH_BMESH) { + SCULPT_pbvh_clear(ob); + } + + PBVH *pbvh = ss->pbvh; + + if (pbvh) { + BMesh *bm = BKE_pbvh_get_bmesh(pbvh); + + if (!ss->bm) { + ss->bm = bm; + } + else if (ss->bm != bm) { + printf("%s: bmesh differed!\n", __func__); + SCULPT_pbvh_clear(ob); + } + } /* Dynamic topology doesn't ensure selection state is valid, so remove #36280. */ BKE_mesh_mselect_clear(mesh); + bool tag_update = false; - /* Create triangles-only BMesh. */ - BMeshCreateParams create_params{}; - create_params.use_toolflags = false; - ss->bm = BM_mesh_create(&allocsize, &create_params); + if (!ss->bm) { + ss->bm = BKE_sculptsession_empty_bmesh_create(); - BMeshFromMeshParams convert_params{}; - convert_params.calc_face_normal = true; - convert_params.calc_vert_normal = true; - convert_params.use_shapekey = true; - convert_params.active_shapekey = ob->shapenr; - BM_mesh_bm_from_me(ss->bm, mesh, &convert_params); - triangulate(ss->bm); + BMeshFromMeshParams params = {}; + params.use_shapekey = true; + params.active_shapekey = ob->shapenr; - BM_data_layer_ensure_named(ss->bm, &ss->bm->vdata, CD_PROP_FLOAT, ".sculpt_mask"); + BM_mesh_bm_from_me(ss->bm, mesh, ¶ms); + tag_update = true; + + BM_data_layer_ensure_named(ss->bm, &ss->bm->vdata, CD_PROP_FLOAT, ".sculpt_mask"); + } /* Make sure the data for existing faces are initialized. */ if (mesh->faces_num != ss->bm->totface) { BM_mesh_normals_update(ss->bm); } +#ifndef DYNTOPO_DYNAMIC_TESS + SCULPT_dynamic_topology_triangulate(ss, ss->bm); +#endif + + if (ss->pbvh) { + BKE_sculptsession_update_attr_refs(ob); + } + + /* XXX Delete this block of code? Might be old fake quadrangulation edge hiding. */ + BMIter iter; + BMEdge *e; + BM_ITER_MESH (e, &iter, ss->bm, BM_EDGES_OF_MESH) { + e->head.hflag |= BM_ELEM_DRAW; + } + + /* Calculate normals. */ + BM_mesh_normals_update(ss->bm); + /* Enable dynamic topology. */ mesh->flag |= ME_SCULPT_DYNAMIC_TOPOLOGY; + BKE_sculpt_ensure_idmap(ob); + /* Enable logging for undo/redo. */ - ss->bm_log = BM_log_create(ss->bm); + if (!ss->bm_log) { + ss->bm_log = BM_log_create(ss->bm, ss->bm_idmap); + } + + tag_update |= !ss->pbvh || BKE_pbvh_type(ss->pbvh) != PBVH_BMESH; /* Update dependency graph, so modifiers that depend on dyntopo being enabled * are re-evaluated and the PBVH is re-created. */ - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - BKE_scene_graph_update_tagged(depsgraph, bmain); + if (tag_update) { + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + BKE_scene_graph_update_tagged(depsgraph, bmain); + } + + /* ss->pbvh should exist by this point. */ + + if (ss->pbvh) { + BKE_sculpt_ensure_sculpt_layers(ob); + BKE_sculpt_ensure_origco(ob); + blender::bke::paint::load_all_original(ob); + } + + SCULPT_update_all_valence_boundary(ob); + + if (ss->pbvh && SCULPT_has_persistent_base(ss)) { + SCULPT_ensure_persistent_layers(ss, ob); + } + + if (!CustomData_has_layer_named(&ss->bm->vdata, CD_PROP_FLOAT, ".sculpt_mask")) { + BM_data_layer_add_named(ss->bm, &ss->bm->vdata, CD_PROP_FLOAT, ".sculpt_mask"); + BKE_sculptsession_update_attr_refs(ob); + } } /* Free the sculpt BMesh and BMLog @@ -123,10 +265,13 @@ void enable_ex(Main *bmain, Depsgraph *depsgraph, Object *ob) * If 'unode' is given, the BMesh's data is copied out to the unode * before the BMesh is deleted so that it can be restored from. */ static void SCULPT_dynamic_topology_disable_ex( - Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob, undo::Node *unode) + Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob, undo::Node * /*unode*/) { SculptSession *ss = ob->sculpt; - Mesh *mesh = static_cast(ob->data); + Mesh *mesh = BKE_object_get_original_mesh(ob); + + /* Destroy temporary layers. */ + BKE_sculpt_attribute_destroy_temporary_all(ob); if (ss->attrs.dyntopo_node_id_vertex) { BKE_sculpt_attribute_destroy(ob, ss->attrs.dyntopo_node_id_vertex); @@ -136,54 +281,38 @@ static void SCULPT_dynamic_topology_disable_ex( BKE_sculpt_attribute_destroy(ob, ss->attrs.dyntopo_node_id_face); } + BKE_sculptsession_update_attr_refs(ob); + BKE_sculptsession_bm_to_me(ob, true); SCULPT_pbvh_clear(ob); - if (unode) { - /* Free all existing custom data. */ - BKE_mesh_clear_geometry(mesh); - - /* Copy over stored custom data. */ - undo::NodeGeometry *geometry = &unode->geometry_bmesh_enter; - mesh->verts_num = geometry->totvert; - mesh->corners_num = geometry->totloop; - mesh->faces_num = geometry->faces_num; - mesh->edges_num = geometry->totedge; - mesh->totface_legacy = 0; - CustomData_copy(&geometry->vert_data, &mesh->vert_data, CD_MASK_MESH.vmask, geometry->totvert); - CustomData_copy(&geometry->edge_data, &mesh->edge_data, CD_MASK_MESH.emask, geometry->totedge); - CustomData_copy( - &geometry->corner_data, &mesh->corner_data, CD_MASK_MESH.lmask, geometry->totloop); - CustomData_copy( - &geometry->face_data, &mesh->face_data, CD_MASK_MESH.pmask, geometry->faces_num); - implicit_sharing::copy_shared_pointer(geometry->face_offset_indices, - geometry->face_offsets_sharing_info, - &mesh->face_offset_indices, - &mesh->runtime->face_offsets_sharing_info); - } - else { - BKE_sculptsession_bm_to_me(ob, true); - - /* Sync the visibility to vertices manually as `vert_to_face_map` is still not initialized. */ - bool *hide_vert = (bool *)CustomData_get_layer_named_for_write( - &mesh->vert_data, CD_PROP_BOOL, ".hide_vert", mesh->verts_num); - if (hide_vert != nullptr) { - memset(hide_vert, 0, sizeof(bool) * mesh->verts_num); - } + /* Sync the visibility to vertices manually as the pmap is still not initialized. */ + bool *hide_vert = (bool *)CustomData_get_layer_named_for_write( + &mesh->vert_data, CD_PROP_BOOL, ".hide_vert", mesh->verts_num); + if (hide_vert != nullptr) { + memset(static_cast(hide_vert), 0, sizeof(bool) * mesh->verts_num); } /* Clear data. */ mesh->flag &= ~ME_SCULPT_DYNAMIC_TOPOLOGY; - /* Typically valid but with global-undo they can be nullptr, see: #36234. */ - if (ss->bm) { - BM_mesh_free(ss->bm); - ss->bm = nullptr; + if (ss->bm_idmap) { + BM_idmap_destroy(ss->bm_idmap); + ss->bm_idmap = nullptr; } + if (ss->bm_log) { BM_log_free(ss->bm_log); ss->bm_log = nullptr; } + /* Typically valid but with global-undo they can be nullptr, see: T36234. */ + if (ss->bm) { + BM_mesh_free(ss->bm); + ss->bm = nullptr; + } + + SCULPT_pbvh_clear(ob); + BKE_particlesystem_reset_all(ob); BKE_ptcache_object_reset(scene, ob, PTCACHE_RESET_OUTDATED); @@ -216,12 +345,15 @@ void disable_with_undo(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object * if (use_undo) { undo::push_end(ob); } + + ss->active_vertex.i = ss->active_face.i = PBVH_REF_NONE; } } static void sculpt_dynamic_topology_enable_with_undo(Main *bmain, Depsgraph *depsgraph, Object *ob) { SculptSession *ss = ob->sculpt; + if (ss->bm == nullptr) { /* May be false in background mode. */ const bool use_undo = G.background ? (ED_undo_stack_get() != nullptr) : true; @@ -233,6 +365,8 @@ static void sculpt_dynamic_topology_enable_with_undo(Main *bmain, Depsgraph *dep undo::push_node(ob, nullptr, undo::Type::DyntopoBegin); undo::push_end(ob); } + + ss->active_vertex.i = ss->active_face.i = 0; } } @@ -259,19 +393,30 @@ static int sculpt_dynamic_topology_toggle_exec(bContext *C, wmOperator * /*op*/) return OPERATOR_FINISHED; } -static int dyntopo_warning_popup(bContext *C, wmOperatorType *ot, enum WarnFlag flag) +static int dyntopo_error_popup(bContext *C, wmOperatorType * /*ot*/, enum WarnFlag flag) { - uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("Warning!"), ICON_ERROR); + uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("Error!"), ICON_ERROR); uiLayout *layout = UI_popup_menu_layout(pup); - if (flag & (VDATA | EDATA | LDATA)) { - const char *msg_error = RPT_("Attribute Data Detected"); - const char *msg = RPT_("Dyntopo will not preserve colors, UVs, or other attributes"); + if (flag & MULTIRES) { + const char *msg_error = TIP_("Multires modifier detected; cannot enable dyntopo."); + const char *msg = TIP_("Dyntopo and multires cannot be mixed."); + uiItemL(layout, msg_error, ICON_INFO); uiItemL(layout, msg, ICON_NONE); uiItemS(layout); } + UI_popup_menu_end(C, pup); + + return OPERATOR_INTERFACE; +} + +static int dyntopo_warning_popup(bContext *C, wmOperatorType *ot, enum WarnFlag flag) +{ + uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("Warning!"), ICON_ERROR); + uiLayout *layout = UI_popup_menu_layout(pup); + if (flag & MODIFIER) { const char *msg_error = RPT_("Generative Modifiers Detected!"); const char *msg = RPT_( @@ -290,26 +435,8 @@ static int dyntopo_warning_popup(bContext *C, wmOperatorType *ot, enum WarnFlag return OPERATOR_INTERFACE; } -static bool dyntopo_supports_layer(const CustomDataLayer &layer) -{ - if (CD_TYPE_AS_MASK(layer.type) & CD_MASK_PROP_ALL) { - /* Some data is stored as generic attributes on #Mesh but in flags or fields on #BMesh. */ - return BM_attribute_stored_in_bmesh_builtin(layer.name); - } - /* Some layers just encode #Mesh topology or are handled as special cases for dyntopo. */ - return ELEM(layer.type, CD_ORIGINDEX); -} - -static bool dyntopo_supports_customdata_layers(const Span layers) -{ - return std::all_of(layers.begin(), layers.end(), [&](const CustomDataLayer &layer) { - return dyntopo_supports_layer(layer); - }); -} - enum WarnFlag check_attribute_warning(Scene *scene, Object *ob) { - Mesh *mesh = static_cast(ob->data); SculptSession *ss = ob->sculpt; WarnFlag flag = WarnFlag(0); @@ -317,20 +444,6 @@ enum WarnFlag check_attribute_warning(Scene *scene, Object *ob) BLI_assert(ss->bm == nullptr); UNUSED_VARS_NDEBUG(ss); - if (!dyntopo_supports_customdata_layers({mesh->vert_data.layers, mesh->vert_data.totlayer})) { - flag |= VDATA; - } - if (!dyntopo_supports_customdata_layers({mesh->edge_data.layers, mesh->edge_data.totlayer})) { - flag |= EDATA; - } - if (!dyntopo_supports_customdata_layers({mesh->face_data.layers, mesh->face_data.totlayer})) { - flag |= LDATA; - } - if (!dyntopo_supports_customdata_layers({mesh->corner_data.layers, mesh->corner_data.totlayer})) - { - flag |= LDATA; - } - { VirtualModifierData virtual_modifier_data; ModifierData *md = BKE_modifiers_get_virtual_modifierlist(ob, &virtual_modifier_data); @@ -342,6 +455,10 @@ enum WarnFlag check_attribute_warning(Scene *scene, Object *ob) continue; } + if (md->type == eModifierType_Multires) { + flag |= MULTIRES; + } + if (mti->type == ModifierTypeType::Constructive) { flag |= MODIFIER; break; @@ -363,7 +480,10 @@ static int sculpt_dynamic_topology_toggle_invoke(bContext *C, Scene *scene = CTX_data_scene(C); const WarnFlag flag = check_attribute_warning(scene, ob); - if (flag) { + if (flag & MULTIRES) { + return dyntopo_error_popup(C, op->type, flag); + } + else if (flag) { /* The mesh has customdata that will be lost, let the user confirm this is OK. */ return dyntopo_warning_popup(C, op->type, flag); } @@ -377,7 +497,10 @@ void SCULPT_OT_dynamic_topology_toggle(wmOperatorType *ot) /* Identifiers. */ ot->name = "Dynamic Topology Toggle"; ot->idname = "SCULPT_OT_dynamic_topology_toggle"; - ot->description = "Dynamic topology alters the mesh topology while sculpting"; + ot->description = + "Dynamic mode; note that you must now check the DynTopo" + "option to enable dynamic remesher (which updates topology will sculpting)" + "this is on by default."; /* API callbacks. */ ot->invoke = sculpt_dynamic_topology_toggle_invoke; diff --git a/source/blender/editors/sculpt_paint/sculpt_expand.cc b/source/blender/editors/sculpt_paint/sculpt_expand.cc index d86bd417ff7..60a7c5c17ae 100644 --- a/source/blender/editors/sculpt_paint/sculpt_expand.cc +++ b/source/blender/editors/sculpt_paint/sculpt_expand.cc @@ -46,6 +46,8 @@ #include "IMB_imbuf.hh" #include "bmesh.hh" +using namespace blender; +using namespace blender::ed::sculpt_paint; namespace blender::ed::sculpt_paint::expand { @@ -57,7 +59,7 @@ namespace blender::ed::sculpt_paint::expand { * to generate these falloff values which will create different patterns in the result when using * the operator. These falloff values require algorithms that rely on mesh connectivity, so they * are only valid on parts of the mesh that are in the same connected component as the given - * initial vertices. If needed, these falloff values are propagated from vertex or grids into the + * initial verts. If needed, these falloff values are propagated from vertex or grids into the * base mesh faces. * * - On each modal callback, the operator gets the active vertex and face and gets its falloff @@ -84,7 +86,7 @@ namespace blender::ed::sculpt_paint::expand { /** * This threshold offsets the required falloff value to start a new loop. This is needed because in - * some situations, vertices which have the same falloff value as max_falloff will start a new + * some situations, verts which have the same falloff value as max_falloff will start a new * loop, which is undesired. */ #define SCULPT_EXPAND_LOOP_THRESHOLD 0.00001f @@ -119,7 +121,7 @@ enum { SCULPT_EXPAND_MODAL_TEXTURE_DISTORTION_DECREASE, }; -/* Functions for getting the state of mesh elements (vertices and base mesh faces). When the main +/* Functions for getting the state of mesh elements (verts and base mesh faces). When the main * functions for getting the state of an element return true it means that data associated to that * element will be modified by expand. */ @@ -144,21 +146,22 @@ static bool sculpt_expand_is_vert_in_active_component(const SculptSession *ss, */ static bool sculpt_expand_is_face_in_active_component(const SculptSession *ss, const Cache *expand_cache, - const int f) + const PBVHFaceRef f) { PBVHVertRef vertex; switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: - vertex.i = ss->corner_verts[ss->faces[f].start()]; + vertex.i = ss->corner_verts[ss->faces[f.i].start()]; break; case PBVH_GRIDS: { const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - vertex.i = ss->faces[f].start() * key->grid_area; + vertex.i = ss->faces[f.i].start() * key->grid_area; break; } case PBVH_BMESH: { - vertex.i = reinterpret_cast(ss->bm->ftable[f]->l_first->v); + BMFace *face = reinterpret_cast(f.i); + vertex.i = reinterpret_cast(face->l_first->v); break; } } @@ -173,7 +176,7 @@ static float sculpt_expand_falloff_value_vertex_get(const SculptSession *ss, const Cache *expand_cache, const PBVHVertRef v) { - int v_i = BKE_pbvh_vertex_to_index(ss->pbvh, v); + const int v_i = BKE_pbvh_vertex_to_index(ss->pbvh, v); if (expand_cache->texture_distortion_strength == 0.0f) { return expand_cache->vert_falloff[v_i]; @@ -267,12 +270,12 @@ static bool sculpt_expand_state_get(SculptSession *ss, Cache *expand_cache, cons * Returns true when the target data should be modified by expand. */ static bool sculpt_expand_face_state_get(SculptSession *ss, - const Span hide_poly, - const Span face_sets, - Cache *expand_cache, - const int f) + expand::Cache *expand_cache, + const PBVHFaceRef f) { - if (!hide_poly.is_empty() && hide_poly[f]) { + int f_i = BKE_pbvh_face_to_index(ss->pbvh, f); + + if (ss->hide_poly && ss->hide_poly[f_i]) { return false; } @@ -290,20 +293,20 @@ static bool sculpt_expand_face_state_get(SculptSession *ss, bool enabled = false; if (expand_cache->snap_enabled_face_sets) { - const int face_set = expand_cache->original_face_sets[f]; - enabled = expand_cache->snap_enabled_face_sets->contains(face_set); + const int faceset = expand_cache->original_face_sets[f_i]; + enabled = expand_cache->snap_enabled_face_sets->contains(faceset); } else { const float loop_len = (expand_cache->max_face_falloff / expand_cache->loop_count) + SCULPT_EXPAND_LOOP_THRESHOLD; const float active_factor = fmod(expand_cache->active_falloff, loop_len); - const float falloff_factor = fmod(expand_cache->face_falloff[f], loop_len); - enabled = falloff_factor < active_factor; + const float falloff_factor = fmod(expand_cache->face_falloff[f_i], loop_len); + enabled = falloff_factor <= active_factor; } if (expand_cache->falloff_type == SCULPT_EXPAND_FALLOFF_ACTIVE_FACE_SET) { - if (face_sets[f] == expand_cache->initial_active_face_set) { + if (SCULPT_face_set_get(ss, f) == expand_cache->initial_active_face_set) { enabled = false; } } @@ -354,7 +357,7 @@ static float sculpt_expand_gradient_value_get(SculptSession *ss, return BKE_brush_curve_strength(expand_cache->brush, linear_falloff, 1.0f); } -/* Utility functions for getting all vertices state during expand. */ +/* Utility functions for getting all verts state during expand. */ /** * Returns a bitmap indexed by vertex index which contains if the vertex was enabled or not for a @@ -373,7 +376,7 @@ static BitVector<> sculpt_expand_bitmap_from_enabled(SculptSession *ss, Cache *e /** * Returns a bitmap indexed by vertex index which contains if the vertex is in the boundary of the - * enabled vertices. This is defined as vertices that are enabled and at least have one connected + * enabled verts. This is defined as verts that are enabled and at least have one connected * vertex that is not enabled. */ static BitVector<> sculpt_expand_boundary_from_enabled(SculptSession *ss, @@ -384,12 +387,11 @@ static BitVector<> sculpt_expand_boundary_from_enabled(SculptSession *ss, BitVector<> boundary_verts(totvert); for (int i = 0; i < totvert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); if (!enabled_verts[i]) { continue; } - PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); - bool is_expand_boundary = false; SculptVertexNeighborIter ni; SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { @@ -399,7 +401,7 @@ static BitVector<> sculpt_expand_boundary_from_enabled(SculptSession *ss, } SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); - if (use_mesh_boundary && SCULPT_vertex_is_boundary(ss, vertex)) { + if (use_mesh_boundary && SCULPT_vertex_is_boundary(ss, vertex, SCULPT_BOUNDARY_MESH)) { is_expand_boundary = true; } @@ -562,8 +564,8 @@ static float *sculpt_expand_normal_falloff_create(Object *ob, for (int repeat = 0; repeat < blur_steps; repeat++) { for (int i = 0; i < totvert; i++) { PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); - float avg = 0.0f; + SculptVertexNeighborIter ni; SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { avg += dists[ni.index]; @@ -606,12 +608,12 @@ static float *sculpt_expand_spherical_falloff_create(Object *ob, const PBVHVertR } const PBVHVertRef symm_vertex = sculpt_expand_get_vertex_index_for_symmetry_pass( ob, symm_it, v); - if (symm_vertex.i != SCULPT_EXPAND_VERTEX_NONE) { + if (symm_vertex.i != -1) { const float *co = SCULPT_vertex_co_get(ss, symm_vertex); for (int i = 0; i < totvert; i++) { - PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); - - dists[i] = min_ff(dists[i], len_v3v3(co, SCULPT_vertex_co_get(ss, vertex))); + dists[i] = min_ff( + dists[i], + len_v3v3(co, SCULPT_vertex_co_get(ss, BKE_pbvh_index_to_vertex(ss->pbvh, i)))); } } } @@ -632,7 +634,7 @@ static float *sculpt_expand_boundary_topology_falloff_create(Object *ob, const P BitVector<> visited_verts(totvert); std::queue queue; - /* Search and initialize a boundary per symmetry pass, then mark those vertices as visited. */ + /* Search and initialize a boundary per symmetry pass, then mark those verts as visited. */ const char symm = SCULPT_mesh_symmetry_xyz_get(ob); for (char symm_it = 0; symm_it <= symm; symm_it++) { if (!SCULPT_is_symmetry_iteration_valid(symm_it, symm)) { @@ -671,6 +673,9 @@ static float *sculpt_expand_boundary_topology_falloff_create(Object *ob, const P if (visited_verts[ni.index]) { continue; } + + const int v_next_i = BKE_pbvh_vertex_to_index(ss->pbvh, v_next); + dists[ni.index] = dists[v_next_i] + 1.0f; visited_verts[ni.index].set(); queue.push(ni.vertex); @@ -693,12 +698,17 @@ static float *sculpt_expand_diagonals_falloff_create(Object *ob, const PBVHVertR float *dists = static_cast(MEM_calloc_arrayN(totvert, sizeof(float), __func__)); /* This algorithm uses mesh data (faces and loops), so this falloff type can't be initialized for - * Multires. It also does not make sense to implement it for dyntopo as the result will be the - * same as Topology falloff. */ - if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) { + * Multires. Also supports non-tri PBVH_BMESH, though untested until we implement that properly*/ + if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES || (ss->bm && ss->bm->totloop != ss->bm->totvert * 3)) + { return dists; } + if (ss->bm) { + BM_mesh_elem_index_ensure(ss->bm, BM_VERT); + BM_mesh_elem_table_ensure(ss->bm, BM_VERT); + } + /* Search and mask as visited the initial vertices using the enabled symmetry passes. */ BitVector<> visited_verts(totvert); std::queue queue; @@ -727,15 +737,42 @@ static float *sculpt_expand_diagonals_falloff_create(Object *ob, const PBVHVertR int v_next_i = BKE_pbvh_vertex_to_index(ss->pbvh, v_next); - for (const int face : ss->vert_to_face_map[v_next_i]) { - for (const int vert : ss->corner_verts.slice(ss->faces[face])) { - const PBVHVertRef neighbor_v = BKE_pbvh_make_vref(vert); - if (visited_verts[neighbor_v.i]) { - continue; + if (ss->bm) { + BMIter iter; + BMFace *f; + BMVert *v = (BMVert *)v_next.i; + + BM_ITER_ELEM (f, &iter, v, BM_FACES_OF_VERT) { + BMLoop *l = f->l_first; + + do { + BMVert *neighbor_v = l->next->v; + const int neighbor_v_i = BM_elem_index_get(neighbor_v); + + if (visited_verts[neighbor_v_i].test()) { + l = l->next; + continue; + } + + dists[neighbor_v_i] = dists[v_next_i] + 1.0f; + visited_verts[neighbor_v_i].set(); + queue.push({reinterpret_cast(neighbor_v)}); + + l = l->next; + } while (l != f->l_first); + } + } + else { + for (const int face : ss->vert_to_face_map[v_next_i]) { + for (const int vert : ss->corner_verts.slice(ss->faces[face])) { + const PBVHVertRef neighbor_v = BKE_pbvh_make_vref(vert); + if (visited_verts[neighbor_v.i]) { + continue; + } + dists[neighbor_v.i] = dists[v_next_i] + 1.0f; + visited_verts[neighbor_v.i].set(); + queue.push(neighbor_v); } - dists[neighbor_v.i] = dists[v_next_i] + 1.0f; - visited_verts[neighbor_v.i].set(); - queue.push(neighbor_v); } } } @@ -754,13 +791,14 @@ static void sculpt_expand_update_max_vert_falloff_value(SculptSession *ss, Cache { const int totvert = SCULPT_vertex_count_get(ss); expand_cache->max_vert_falloff = -FLT_MAX; - for (int i = 0; i < totvert; i++) { - PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + for (int i = 0; i < totvert; i++) { if (expand_cache->vert_falloff[i] == FLT_MAX) { continue; } + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + if (!sculpt_expand_is_vert_in_active_component(ss, expand_cache, vertex)) { continue; } @@ -779,11 +817,13 @@ static void sculpt_expand_update_max_face_falloff_factor(SculptSession *ss, Cach const int totface = ss->totfaces; expand_cache->max_face_falloff = -FLT_MAX; for (int i = 0; i < totface; i++) { + PBVHFaceRef f = BKE_pbvh_index_to_face(ss->pbvh, i); + if (expand_cache->face_falloff[i] == FLT_MAX) { continue; } - if (!sculpt_expand_is_face_in_active_component(ss, expand_cache, i)) { + if (!sculpt_expand_is_face_in_active_component(ss, expand_cache, f)) { continue; } @@ -793,10 +833,10 @@ static void sculpt_expand_update_max_face_falloff_factor(SculptSession *ss, Cach } /** - * Functions to get falloff values for faces from the values from the vertices. This is used for - * expanding Face Sets. Depending on the data type of the #SculptSession, this needs to get the per - * face falloff value from the connected vertices of each face or from the grids stored per loops - * for each face. + * Functions to get falloff values for faces from the values from the verts. This is used for + * expanding Face Sets. Depending on the data type of the #SculptSession, this needs to get the + * per face falloff value from the connected verts of each face or from the grids stored per + * loops for each face. */ static void sculpt_expand_grids_to_faces_falloff(SculptSession *ss, Mesh *mesh, @@ -831,6 +871,23 @@ static void sculpt_expand_vertex_to_faces_falloff(Mesh *mesh, Cache *expand_cach } } +static void sculpt_expand_vertex_to_faces_falloff_bmesh(BMesh *bm, expand::Cache *expand_cache) +{ + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + BMLoop *l = f->l_first; + + float accum = 0.0f; + + do { + accum += expand_cache->vert_falloff[BM_elem_index_get(l->v)]; + l = l->next; + } while (l != f->l_first); + + expand_cache->face_falloff[BM_elem_index_get(f)] = accum / f->len; + } +} /** * Main function to update the faces falloff from a already calculated vertex falloff. */ @@ -845,14 +902,16 @@ static void sculpt_expand_mesh_face_falloff_from_vertex_falloff(SculptSession *s MEM_malloc_arrayN(mesh->faces_num, sizeof(float), __func__)); } - if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) { - sculpt_expand_vertex_to_faces_falloff(mesh, expand_cache); - } - else if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { - sculpt_expand_grids_to_faces_falloff(ss, mesh, expand_cache); - } - else { - BLI_assert(false); + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + sculpt_expand_vertex_to_faces_falloff(mesh, expand_cache); + break; + case PBVH_GRIDS: + sculpt_expand_grids_to_faces_falloff(ss, mesh, expand_cache); + break; + case PBVH_BMESH: + sculpt_expand_vertex_to_faces_falloff_bmesh(ss->bm, expand_cache); + break; } } @@ -860,15 +919,15 @@ static void sculpt_expand_mesh_face_falloff_from_vertex_falloff(SculptSession *s * from the current Cache options and falloff values. */ /** - * Geodesic recursion: Initializes falloff values using geodesic distances from the boundary of the - * current vertices state. + * Geodesic recursion: Initializes falloff values using geodesic distances from the boundary of + * the current verts state. */ static void sculpt_expand_geodesics_from_state_boundary(Object *ob, Cache *expand_cache, const BitSpan enabled_verts) { SculptSession *ss = ob->sculpt; - BLI_assert(BKE_pbvh_type(ss->pbvh) == PBVH_FACES); + BLI_assert(ELEM(BKE_pbvh_type(ss->pbvh), PBVH_GRIDS, PBVH_FACES, PBVH_BMESH)); GSet *initial_verts = BLI_gset_int_new("initial_verts"); const BitVector<> boundary_verts = sculpt_expand_boundary_from_enabled(ss, enabled_verts, false); @@ -889,7 +948,7 @@ static void sculpt_expand_geodesics_from_state_boundary(Object *ob, /** * Topology recursion: Initializes falloff values using topology steps from the boundary of the - * current vertices state, increasing the value by 1 each time a new vertex is visited. + * current verts state, increasing the value by 1 each time a new vertex is visited. */ static void sculpt_expand_topology_from_state_boundary(Object *ob, Cache *expand_cache, @@ -912,7 +971,7 @@ static void sculpt_expand_topology_from_state_boundary(Object *ob, } PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); - flood_fill::add_and_skip_initial(&flood, vertex); + flood_fill::add_and_skip_initial(ss, &flood, vertex); } ExpandFloodFillData fdata; @@ -936,9 +995,9 @@ static void sculpt_expand_resursion_step_add(Object *ob, const BitVector<> enabled_verts = sculpt_expand_bitmap_from_enabled(ss, expand_cache); - /* Each time a new recursion step is created, reset the distortion strength. This is the expected - * result from the recursion, as otherwise the new falloff will render with undesired distortion - * from the beginning. */ + /* Each time a new recursion step is created, reset the distortion strength. This is the + * expected result from the recursion, as otherwise the new falloff will render with undesired + * distortion from the beginning. */ expand_cache->texture_distortion_strength = 0.0f; switch (recursion_type) { @@ -961,8 +1020,8 @@ static void sculpt_expand_resursion_step_add(Object *ob, /* Face Set Boundary falloff. */ /** - * When internal falloff is set to true, the falloff will fill the active Face Set with a gradient, - * otherwise the active Face Set will be filled with a constant falloff of 0.0f. + * When internal falloff is set to true, the falloff will fill the active Face Set with a + * gradient, otherwise the active Face Set will be filled with a constant falloff of 0.0f. */ static void sculpt_expand_initialize_from_face_set_boundary(Object *ob, Cache *expand_cache, @@ -972,7 +1031,7 @@ static void sculpt_expand_initialize_from_face_set_boundary(Object *ob, SculptSession *ss = ob->sculpt; const int totvert = SCULPT_vertex_count_get(ss); - BitVector<> enabled_verts(totvert); + blender::BitVector<> enabled_verts(totvert); for (int i = 0; i < totvert; i++) { PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); @@ -985,6 +1044,7 @@ static void sculpt_expand_initialize_from_face_set_boundary(Object *ob, enabled_verts[i].set(); } + /* TODO: Default to topology. */ if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) { sculpt_expand_geodesics_from_state_boundary(ob, expand_cache, enabled_verts); } @@ -1037,13 +1097,16 @@ static void sculpt_expand_falloff_factors_from_vertex_and_symm_create( expand_cache->falloff_type = falloff_type; SculptSession *ss = ob->sculpt; - const bool has_topology_info = BKE_pbvh_type(ss->pbvh) == PBVH_FACES; + const bool has_topology_info = ELEM(BKE_pbvh_type(ss->pbvh), PBVH_FACES, PBVH_BMESH); switch (falloff_type) { case SCULPT_EXPAND_FALLOFF_GEODESIC: + expand_cache->vert_falloff = sculpt_expand_geodesic_falloff_create(ob, v); + /* expand_cache->vert_falloff = has_topology_info ? sculpt_expand_geodesic_falloff_create(ob, v) : sculpt_expand_spherical_falloff_create(ob, v); + */ break; case SCULPT_EXPAND_FALLOFF_TOPOLOGY: expand_cache->vert_falloff = sculpt_expand_topology_falloff_create(ob, v); @@ -1140,8 +1203,8 @@ static void sculpt_expand_cache_data_free(Cache *expand_cache) static void sculpt_expand_cache_free(SculptSession *ss) { sculpt_expand_cache_data_free(ss->expand_cache); - /* Needs to be set to nullptr as the paint cursor relies on checking this pointer detecting if an - * expand operation is running. */ + /* Needs to be set to nullptr as the paint cursor relies on checking this pointer detecting if + * an expand operation is running. */ ss->expand_cache = nullptr; } @@ -1150,9 +1213,13 @@ static void sculpt_expand_cache_free(SculptSession *ss) */ static void sculpt_expand_restore_face_set_data(Object &object, Cache *expand_cache) { - bke::SpanAttributeWriter face_sets = face_set::ensure_face_sets_mesh(object); - face_sets.span.copy_from(expand_cache->original_face_sets); - face_sets.finish(); + SculptSession *ss = object.sculpt; + + for (int i = 0; i < ss->totfaces; i++) { + PBVHFaceRef f = BKE_pbvh_index_to_face(ss->pbvh, i); + + SCULPT_face_set_set(ss, f, expand_cache->original_face_sets[i]); + } Vector nodes = bke::pbvh::search_gather(object.sculpt->pbvh, {}); for (PBVHNode *node : nodes) { @@ -1323,26 +1390,25 @@ static void sculpt_expand_mask_update_task(SculptSession *ss, */ static void sculpt_expand_face_sets_update(Object &object, Cache *expand_cache) { - bke::SpanAttributeWriter face_sets = face_set::ensure_face_sets_mesh(object); - Mesh &mesh = *static_cast(object.data); - const bke::AttributeAccessor attributes = mesh.attributes(); - const VArraySpan hide_poly = *attributes.lookup(".hide_poly", bke::AttrDomain::Face); - for (const int f : face_sets.span.index_range()) { - const bool enabled = sculpt_expand_face_state_get( - object.sculpt, hide_poly, face_sets.span, expand_cache, f); + SculptSession *ss = object.sculpt; + const int totface = ss->totfaces; + + for (int f_i = 0; f_i < totface; f_i++) { + PBVHFaceRef f = BKE_pbvh_index_to_face(ss->pbvh, f_i); + int fset = SCULPT_face_set_get(ss, f); + + const bool enabled = sculpt_expand_face_state_get(ss, expand_cache, f); if (!enabled) { continue; } if (expand_cache->preserve) { - face_sets.span[f] += expand_cache->next_face_set; + SCULPT_face_set_set(ss, f, fset + expand_cache->next_face_set); } else { - face_sets.span[f] = expand_cache->next_face_set; + SCULPT_face_set_set(ss, f, expand_cache->next_face_set); } } - face_sets.finish(); - for (PBVHNode *node : expand_cache->nodes) { BKE_pbvh_node_mark_update_face_sets(node); } @@ -1420,12 +1486,13 @@ static void sculpt_expand_flush_updates(bContext *C) static void sculpt_expand_original_state_store(Object *ob, Cache *expand_cache) { SculptSession *ss = ob->sculpt; - Mesh &mesh = *static_cast(ob->data); const int totvert = SCULPT_vertex_count_get(ss); + SCULPT_vertex_random_access_ensure(ss); + /* Face Sets are always stored as they are needed for snapping. */ - expand_cache->initial_face_sets = face_set::duplicate_face_sets(mesh); - expand_cache->original_face_sets = face_set::duplicate_face_sets(mesh); + expand_cache->initial_face_sets = face_set::duplicate_face_sets(*ob); + expand_cache->original_face_sets = face_set::duplicate_face_sets(*ob); if (expand_cache->target == SCULPT_EXPAND_TARGET_MASK) { expand_cache->original_mask = mask::duplicate_mask(*ob); @@ -1447,20 +1514,18 @@ static void sculpt_expand_original_state_store(Object *ob, Cache *expand_cache) */ static void sculpt_expand_face_sets_restore(Object &object, Cache *expand_cache) { - SculptSession &ss = *object.sculpt; - bke::SpanAttributeWriter face_sets = face_set::ensure_face_sets_mesh(object); - const int totfaces = ss.totfaces; + SculptSession *ss = object.sculpt; + const int totfaces = ss->totfaces; + for (int i = 0; i < totfaces; i++) { + PBVHFaceRef fref = BKE_pbvh_index_to_face(ss->pbvh, i); + if (expand_cache->original_face_sets[i] <= 0) { /* Do not modify hidden Face Sets, even when restoring the IDs state. */ continue; } - if (!sculpt_expand_is_face_in_active_component(&ss, expand_cache, i)) { - continue; - } - face_sets.span[i] = expand_cache->initial_face_sets[i]; + SCULPT_face_set_set(ss, fref, expand_cache->initial_face_sets[i]); } - face_sets.finish(); } static void sculpt_expand_update_for_vertex(bContext *C, Object *ob, const PBVHVertRef vertex) @@ -1468,12 +1533,12 @@ static void sculpt_expand_update_for_vertex(bContext *C, Object *ob, const PBVHV SculptSession *ss = ob->sculpt; Cache *expand_cache = ss->expand_cache; - int vertex_i = BKE_pbvh_vertex_to_index(ss->pbvh, vertex); + const int vertex_i = BKE_pbvh_vertex_to_index(ss->pbvh, vertex); /* Update the active factor in the cache. */ if (vertex.i == SCULPT_EXPAND_VERTEX_NONE) { /* This means that the cursor is not over the mesh, so a valid active falloff can't be - * determined. In this situations, don't evaluate enabled states and default all vertices in + * determined. In this situations, don't evaluate enabled states and default all verts in * connected components to enabled. */ expand_cache->active_falloff = expand_cache->max_vert_falloff; expand_cache->all_enabled = true; @@ -1527,11 +1592,13 @@ static PBVHVertRef sculpt_expand_target_vertex_update_and_get(bContext *C, if (SCULPT_cursor_geometry_info_update(C, &sgi, mval, false)) { return SCULPT_active_vertex_get(ss); } - return BKE_pbvh_make_vref(SCULPT_EXPAND_VERTEX_NONE); + + PBVHVertRef ret = {SCULPT_EXPAND_VERTEX_NONE}; + return ret; } /** - * Moves the sculpt pivot to the average point of the boundary enabled vertices of the current + * Moves the sculpt pivot to the average point of the boundary enabled verts of the current * expand state. Take symmetry and active components into account. */ static void sculpt_expand_reposition_pivot(bContext *C, Object *ob, Cache *expand_cache) @@ -1544,11 +1611,11 @@ static void sculpt_expand_reposition_pivot(bContext *C, Object *ob, Cache *expan expand_cache->invert = false; const BitVector<> enabled_verts = sculpt_expand_bitmap_from_enabled(ss, expand_cache); - /* For boundary topology, position the pivot using only the boundary of the enabled vertices, - * without taking mesh boundary into account. This allows to create deformations like bending the - * mesh from the boundary of the mask that was just created. */ - const float use_mesh_boundary = expand_cache->falloff_type != - SCULPT_EXPAND_FALLOFF_BOUNDARY_TOPOLOGY; + /* For boundary topology, position the pivot using only the boundary of the enabled verts, + * without taking mesh boundary into account. This allows to create deformations like bending + * the mesh from the boundary of the mask that was just created. */ + const bool use_mesh_boundary = expand_cache->falloff_type != + SCULPT_EXPAND_FALLOFF_BOUNDARY_TOPOLOGY; BitVector<> boundary_verts = sculpt_expand_boundary_from_enabled( ss, enabled_verts, use_mesh_boundary); @@ -1563,12 +1630,12 @@ static void sculpt_expand_reposition_pivot(bContext *C, Object *ob, Cache *expan const float *expand_init_co = SCULPT_vertex_co_get(ss, expand_cache->initial_active_vertex); for (int i = 0; i < totvert; i++) { - PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); - if (!boundary_verts[i]) { continue; } + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + if (!sculpt_expand_is_vert_in_active_component(ss, expand_cache, vertex)) { continue; } @@ -1663,16 +1730,13 @@ static void sculpt_expand_set_initial_components_for_mouse(bContext *C, initial_vertex = SCULPT_active_vertex_get(ss); } - int initial_vertex_i = BKE_pbvh_vertex_to_index(ss->pbvh, initial_vertex); - copy_v2_v2(ss->expand_cache->initial_mouse, mval); expand_cache->initial_active_vertex = initial_vertex; - expand_cache->initial_active_vertex_i = initial_vertex_i; expand_cache->initial_active_face_set = face_set::active_face_set_get(ss); if (expand_cache->next_face_set == SCULPT_FACE_SET_NONE) { - /* Only set the next face set once, otherwise this ID will constantly update to a new one each - * time this function is called for using a new initial vertex from a different cursor + /* Only set the next face set once, otherwise this ID will constantly update to a new one + * each time this function is called for using a new initial vertex from a different cursor * position. */ if (expand_cache->modify_active_face_set) { expand_cache->next_face_set = face_set::active_face_set_get(ss); @@ -1688,8 +1752,8 @@ static void sculpt_expand_set_initial_components_for_mouse(bContext *C, } /** - * Displaces the initial mouse coordinates using the new mouse position to get a new active vertex. - * After that, initializes a new falloff of the same type with the new active vertex. + * Displaces the initial mouse coordinates using the new mouse position to get a new active + * vertex. After that, initializes a new falloff of the same type with the new active vertex. */ static void sculpt_expand_move_propagation_origin(bContext *C, Object *ob, @@ -1731,17 +1795,14 @@ static void sculpt_expand_ensure_sculptsession_data(Object *ob) static int sculpt_expand_active_face_set_id_get(SculptSession *ss, Cache *expand_cache) { switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_BMESH: case PBVH_FACES: - return expand_cache->original_face_sets[ss->active_face_index]; + return expand_cache->original_face_sets[BKE_pbvh_face_to_index(ss->pbvh, ss->active_face)]; case PBVH_GRIDS: { const int face_index = BKE_subdiv_ccg_grid_to_face_index(*ss->subdiv_ccg, ss->active_grid_index); return expand_cache->original_face_sets[face_index]; } - case PBVH_BMESH: { - /* Dyntopo does not support Face Set functionality. */ - BLI_assert(false); - } } return SCULPT_FACE_SET_NONE; } @@ -1944,9 +2005,133 @@ static int sculpt_expand_modal(bContext *C, wmOperator *op, const wmEvent *event * The faces that were using the `delete_id` Face Set are filled * using the content from their neighbors. */ -static void sculpt_expand_delete_face_set_id( - int *r_face_sets, SculptSession *ss, Cache *expand_cache, Mesh *mesh, const int delete_id) +static void sculpt_expand_delete_face_set_id_bmesh(int *r_face_sets, + SculptSession *ss, + expand::Cache *expand_cache, + const int delete_id) { + BMIter iter; + BMFace *f; + int i = 0; + const int totface = ss->bm->totface; + + /* Check that all the face sets IDs in the mesh are not equal to `delete_id` + * before attempting to delete it. */ + bool all_same_id = true; + + BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { + PBVHFaceRef fref = {(intptr_t)f}; + i++; + + if (!sculpt_expand_is_face_in_active_component(ss, expand_cache, fref)) { + continue; + } + + if (r_face_sets[i] != delete_id) { + all_same_id = false; + break; + } + } + + if (all_same_id) { + return; + } + + BLI_LINKSTACK_DECLARE(queue, BMFace *); + BLI_LINKSTACK_DECLARE(queue_next, BMFace *); + + BLI_LINKSTACK_INIT(queue); + BLI_LINKSTACK_INIT(queue_next); + + for (int i = 0; i < totface; i++) { + PBVHFaceRef fref = BKE_pbvh_index_to_face(ss->pbvh, i); + + if (r_face_sets[i] == delete_id) { + BLI_LINKSTACK_PUSH(queue, (BMFace *)(fref.i)); + } + } + + while (BLI_LINKSTACK_SIZE(queue)) { + bool any_updated = false; + + while (BLI_LINKSTACK_SIZE(queue)) { + const PBVHFaceRef f = {(intptr_t)(BLI_LINKSTACK_POP(queue))}; + BMFace *bf = (BMFace *)f.i; + const int f_index = BM_elem_index_get(bf); + + int other_id = delete_id; + BMLoop *l = bf->l_first; + do { + BMLoop *l2 = l->radial_next; + do { + const int neighbor_face_index = BM_elem_index_get(l2->f); + + if (expand_cache->original_face_sets[neighbor_face_index] <= 0) { + /* Skip picking IDs from hidden Face Sets. */ + continue; + } + + if (r_face_sets[neighbor_face_index] != delete_id) { + other_id = r_face_sets[neighbor_face_index]; + } + + l2 = l2->radial_next; + } while (l2 != l); + + l = l->next; + } while (l != bf->l_first); + + if (other_id != delete_id) { + any_updated = true; + r_face_sets[f_index] = other_id; + } + else { + BLI_LINKSTACK_PUSH(queue_next, bf); + } + } + + if (!any_updated) { + /* No Face Sets where updated in this iteration, which means that no more content to keep + * filling the faces of the deleted Face Set was found. Break to avoid entering an infinite + * loop trying to search for those faces again. */ + break; + } + + BLI_LINKSTACK_SWAP(queue, queue_next); + } + + BLI_LINKSTACK_FREE(queue); + BLI_LINKSTACK_FREE(queue_next); + + /* Ensure that the visibility state of the modified Face Sets is the same as the original ones. + */ + for (int i = 0; i < totface; i++) { + if (expand_cache->original_face_sets[i] >= 0) { + r_face_sets[i] = abs(r_face_sets[i]); + } + else { + r_face_sets[i] = -abs(r_face_sets[i]); + } + } +} + +/** + * Deletes the `delete_id` Face Set ID from the mesh Face Sets + * and stores the result in `r_face_set`. + * The faces that were using the `delete_id` Face Set are filled + * using the content from their neighbors. + */ +static void sculpt_expand_delete_face_set_id(int *r_face_sets, + SculptSession *ss, + expand::Cache *expand_cache, + Mesh *mesh, + const int delete_id) +{ + if (ss->bm) { + sculpt_expand_delete_face_set_id_bmesh(r_face_sets, ss, expand_cache, delete_id); + return; + } + const int totface = ss->totfaces; const GroupedSpan vert_to_face_map = ss->vert_to_face_map; const OffsetIndices faces = mesh->faces(); @@ -1956,7 +2141,9 @@ static void sculpt_expand_delete_face_set_id( * before attempting to delete it. */ bool all_same_id = true; for (int i = 0; i < totface; i++) { - if (!sculpt_expand_is_face_in_active_component(ss, expand_cache, i)) { + if (!sculpt_expand_is_face_in_active_component( + ss, expand_cache, BKE_pbvh_index_to_face(ss->pbvh, i))) + { continue; } if (r_face_sets[i] != delete_id) { @@ -2026,6 +2213,7 @@ static void sculpt_expand_cache_initial_config_set(bContext *C, /* RNA properties. */ expand_cache->normal_falloff_blur_steps = RNA_int_get(op->ptr, "normal_falloff_smooth"); expand_cache->invert = RNA_boolean_get(op->ptr, "invert"); + expand_cache->preserve_flip_inverse = RNA_boolean_get(op->ptr, "use_preserve_flip_inverse"); expand_cache->preserve = RNA_boolean_get(op->ptr, "use_mask_preserve"); expand_cache->auto_mask = RNA_boolean_get(op->ptr, "use_auto_mask"); expand_cache->falloff_gradient = RNA_boolean_get(op->ptr, "use_falloff_gradient"); @@ -2135,6 +2323,9 @@ static int sculpt_expand_invoke(bContext *C, wmOperator *op, const wmEvent *even SculptSession *ss = ob->sculpt; Mesh *mesh = static_cast(ob->data); + SCULPT_vertex_random_access_ensure(ss); + SCULPT_face_random_access_ensure(ss); + const View3D *v3d = CTX_wm_view3d(C); const Base *base = CTX_data_active_base(C); if (!BKE_base_is_visible(v3d, base)) { @@ -2170,19 +2361,20 @@ static int sculpt_expand_invoke(bContext *C, wmOperator *op, const wmEvent *even BKE_sculpt_update_object_for_edit(depsgraph, ob, needs_colors); - /* Do nothing when the mesh has 0 vertices. */ + /* Do nothing when the mesh has 0 verts. */ const int totvert = SCULPT_vertex_count_get(ss); if (totvert == 0) { sculpt_expand_cache_free(ss); return OPERATOR_CANCELLED; } - /* Face Set operations are not supported in dyntopo. */ - if (ss->expand_cache->target == SCULPT_EXPAND_TARGET_FACE_SETS && - BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) - { - sculpt_expand_cache_free(ss); - return OPERATOR_CANCELLED; + if (ss->expand_cache->target == SCULPT_EXPAND_TARGET_FACE_SETS) { + ss->face_sets = BKE_sculpt_face_sets_ensure(ob); + } + + if (ss->expand_cache->target == SCULPT_EXPAND_TARGET_MASK) { + MultiresModifierData *mmd = BKE_sculpt_multires_active(ss->scene, ob); + BKE_sculpt_mask_layers_ensure(depsgraph, CTX_data_main(C), ob, mmd); } sculpt_expand_ensure_sculptsession_data(ob); @@ -2214,7 +2406,8 @@ static int sculpt_expand_invoke(bContext *C, wmOperator *op, const wmEvent *even RNA_enum_get(op->ptr, "falloff_type")); /* When starting from a boundary vertex, set the initial falloff to boundary. */ - if (SCULPT_vertex_is_boundary(ss, ss->expand_cache->initial_active_vertex)) { + if (SCULPT_vertex_is_boundary(ss, ss->expand_cache->initial_active_vertex, SCULPT_BOUNDARY_MESH)) + { falloff_type = SCULPT_EXPAND_FALLOFF_BOUNDARY_TOPOLOGY; } @@ -2350,6 +2543,12 @@ void SCULPT_OT_expand(wmOperatorType *ot) "Falloff Type", "Initial falloff of the expand operation"); + ot->prop = RNA_def_boolean(ot->srna, + "use_preserve_flip_inverse", + false, + "Preserve Inverted", + "Flip preserve mode in inverse mode"); + ot->prop = RNA_def_boolean( ot->srna, "invert", false, "Invert", "Invert the expand active elements"); ot->prop = RNA_def_boolean(ot->srna, @@ -2382,8 +2581,8 @@ void SCULPT_OT_expand(wmOperatorType *ot) 0, INT_MAX, "Max Vertex Count for Geodesic Move Preview", - "Maximum number of vertices in the mesh for using geodesic falloff when " - "moving the origin of expand. If the total number of vertices is greater " + "Maximum number of verts in the mesh for using geodesic falloff when " + "moving the origin of expand. If the total number of verts is greater " "than this value, the falloff will be set to spherical when moving", 0, 1000000); diff --git a/source/blender/editors/sculpt_paint/sculpt_face_set.cc b/source/blender/editors/sculpt_paint/sculpt_face_set.cc index 960c3392ab8..16ae9e235b6 100644 --- a/source/blender/editors/sculpt_paint/sculpt_face_set.cc +++ b/source/blender/editors/sculpt_paint/sculpt_face_set.cc @@ -29,6 +29,8 @@ #include "DNA_brush_types.h" #include "DNA_customdata_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" @@ -53,63 +55,45 @@ #include "ED_sculpt.hh" -#include "paint_intern.hh" #include "sculpt_intern.hh" #include "RNA_access.hh" #include "RNA_define.hh" #include "bmesh.hh" +#include "bmesh_idmap.hh" + +using blender::Array; +using blender::float3; +using blender::IndexRange; +using blender::Span; +using blender::Vector; namespace blender::ed::sculpt_paint::face_set { +/* Utils. */ -int find_next_available_id(Object &object) +static int mesh_find_next_available_id(Mesh *mesh) { - SculptSession &ss = *object.sculpt; - switch (BKE_pbvh_type(ss.pbvh)) { - case PBVH_FACES: - case PBVH_GRIDS: { - Mesh &mesh = *static_cast(object.data); - const bke::AttributeAccessor attributes = mesh.attributes(); - const VArraySpan face_sets = *attributes.lookup(".sculpt_face_set", + using namespace blender; + const VArray attribute = *mesh->attributes().lookup(".sculpt_face_set", bke::AttrDomain::Face); - const int max = threading::parallel_reduce( - face_sets.index_range(), - 4096, - 1, - [&](const IndexRange range, int max) { - for (const int id : face_sets.slice(range)) { - max = std::max(max, id); - } - return max; - }, - [](const int a, const int b) { return std::max(a, b); }); - return max + 1; - } - case PBVH_BMESH: { - BMesh &bm = *ss.bm; - const int cd_offset = CustomData_get_offset_named( - &bm.pdata, CD_PROP_INT32, ".sculpt_face_set"); - if (cd_offset == -1) { - return 1; - } - int next_face_set = 1; - BMIter iter; - BMFace *f; - BM_ITER_MESH (f, &iter, &bm, BM_FACES_OF_MESH) { - const int fset = *static_cast(POINTER_OFFSET(f->head.data, cd_offset)); - next_face_set = std::max(next_face_set, fset); - } - - return next_face_set + 1; - } + if (!attribute) { + return SCULPT_FACE_SET_NONE; } - BLI_assert_unreachable(); - return 0; + const VArraySpan face_sets(attribute); + + int next_face_set_id = 0; + for (const int i : face_sets.index_range()) { + next_face_set_id = max_ii(next_face_set_id, face_sets[i]); + } + next_face_set_id++; + + return next_face_set_id; } -void initialize_none_to_id(Mesh *mesh, const int new_id) +void initialize_none_to_id(Mesh *mesh, int new_id) { + using namespace blender; bke::MutableAttributeAccessor attributes = mesh->attributes_for_write(); bke::SpanAttributeWriter face_sets = attributes.lookup_for_write_span( ".sculpt_face_set"); @@ -122,7 +106,6 @@ void initialize_none_to_id(Mesh *mesh, const int new_id) face_sets.span[i] = new_id; } } - face_sets.finish(); } int active_update_and_get(bContext *C, Object *ob, const float mval[2]) @@ -150,249 +133,120 @@ bke::SpanAttributeWriter ensure_face_sets_mesh(Object &object) bke::AttributeInitVArray(VArray::ForSingle(1, mesh.faces_num))); mesh.face_sets_color_default = 1; } - object.sculpt->face_sets = static_cast( - CustomData_get_layer_named(&mesh.face_data, CD_PROP_INT32, ".sculpt_face_set")); + object.sculpt->face_sets = static_cast(CustomData_get_layer_named_for_write( + &mesh.face_data, CD_PROP_INT32, ".sculpt_face_set", mesh.faces_num)); return attributes.lookup_or_add_for_write_span(".sculpt_face_set", bke::AttrDomain::Face); } -int ensure_face_sets_bmesh(Object &object) -{ - Mesh &mesh = *static_cast(object.data); - SculptSession &ss = *object.sculpt; - BMesh &bm = *ss.bm; - if (!CustomData_has_layer_named(&bm.pdata, CD_PROP_INT32, ".sculpt_face_set")) { - BM_data_layer_add_named(&bm, &bm.pdata, CD_PROP_INT32, ".sculpt_face_set"); - const int offset = CustomData_get_offset_named(&bm.pdata, CD_PROP_INT32, ".sculpt_face_set"); - if (offset == -1) { - return -1; - } - BMIter iter; - BMFace *face; - BM_ITER_MESH (face, &iter, &bm, BM_FACES_OF_MESH) { - BM_ELEM_CD_SET_INT(face, offset, 1); - } - mesh.face_sets_color_default = 1; - return offset; - } - return CustomData_get_offset_named(&bm.pdata, CD_PROP_INT32, ".sculpt_face_set"); -} - /* Draw Face Sets Brush. */ - constexpr float FACE_SET_BRUSH_MIN_FADE = 0.05f; - -static void do_draw_face_sets_brush_faces(Object *ob, - const Brush *brush, - const Span nodes) -{ - SculptSession *ss = ob->sculpt; - const Span positions = SCULPT_mesh_deformed_positions_get(ss); - - Mesh &mesh = *static_cast(ob->data); - const bke::AttributeAccessor attributes = mesh.attributes(); - const VArraySpan hide_poly = *attributes.lookup(".hide_poly", bke::AttrDomain::Face); - - bke::SpanAttributeWriter attribute = ensure_face_sets_mesh(*ob); - MutableSpan face_sets = attribute.span; - - threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { - const float bstrength = ss->cache->bstrength; - const int thread_id = BLI_task_parallel_thread_id(nullptr); - for (PBVHNode *node : nodes.slice(range)) { - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, brush->falloff_shape); - - auto_mask::NodeData automask_data = auto_mask::node_begin( - *ob, ss->cache->automasking.get(), *node); - - bool changed = false; - - PBVHVertexIter vd; - BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - auto_mask::node_update(automask_data, vd); - - for (const int face_i : ss->vert_to_face_map[vd.index]) { - const IndexRange face = ss->faces[face_i]; - - const float3 poly_center = bke::mesh::face_center_calc(positions, - ss->corner_verts.slice(face)); - - if (!sculpt_brush_test_sq_fn(&test, poly_center)) { - continue; - } - if (!hide_poly.is_empty() && hide_poly[face_i]) { - continue; - } - const float fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - vd.mask, - vd.vertex, - thread_id, - &automask_data); - - if (fade > FACE_SET_BRUSH_MIN_FADE) { - face_sets[face_i] = ss->cache->paint_face_set; - changed = true; - } - } - } - BKE_pbvh_vertex_iter_end; - - if (changed) { - undo::push_node(ob, node, undo::Type::FaceSet); - } - } - }); - attribute.finish(); -} - -static void do_draw_face_sets_brush_grids(Object *ob, - const Brush *brush, - const Span nodes) +static void do_draw_face_sets_brush_task(Object *ob, + const Brush *brush, + bool have_fset_automasking, + bool set_active_faceset, + PBVHNode *node) { + using namespace blender; SculptSession *ss = ob->sculpt; const float bstrength = ss->cache->bstrength; - SubdivCCG &subdiv_ccg = *ss->subdiv_ccg; - bke::SpanAttributeWriter attribute = ensure_face_sets_mesh(*ob); - MutableSpan face_sets = attribute.span; + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(nullptr); - threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { - const int thread_id = BLI_task_parallel_thread_id(nullptr); - for (PBVHNode *node : nodes.slice(range)) { - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, brush->falloff_shape); + const MutableSpan positions = SCULPT_mesh_deformed_positions_get(ss); + auto_mask::NodeData automask_data = auto_mask::node_begin(*ob, ss->cache->automasking.get(), *node); - auto_mask::NodeData automask_data = auto_mask::node_begin( - *ob, ss->cache->automasking.get(), *node); + /* Ensure automasking data is up to date. */ + if (ss->cache->automasking) { + PBVHVertexIter vd; - bool changed = false; + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_ALL) { + auto_mask::node_update(automask_data, vd); + } + BKE_pbvh_vertex_iter_end; + } - PBVHVertexIter vd; - BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - auto_mask::node_update(automask_data, vd); + bool changed = false; - if (!sculpt_brush_test_sq_fn(&test, vd.co)) { - continue; - } - const float fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - vd.mask, - vd.vertex, - thread_id, - &automask_data); + PBVHFaceIter fd; + BKE_pbvh_face_iter_begin (ss->pbvh, node, fd) { + if (SCULPT_face_is_hidden(ss, fd.face)) { + continue; + } - if (fade > FACE_SET_BRUSH_MIN_FADE) { - const int face_index = BKE_subdiv_ccg_grid_to_face_index(subdiv_ccg, - vd.grid_indices[vd.g]); - face_sets[face_index] = ss->cache->paint_face_set; - changed = true; - } + float3 poly_center = {}; + float mask = 0.0; + + for (int i = 0; i < fd.verts_num; i++) { + poly_center += SCULPT_vertex_co_get(ss, fd.verts[i]); + mask += SCULPT_vertex_mask_get(ss, fd.verts[i]); + } + + poly_center /= (float)fd.verts_num; + mask /= (float)fd.verts_num; + + if (!sculpt_brush_test_sq_fn(&test, poly_center)) { + continue; + } + + /* Face set automasking in inverted draw mode is tricky, we have + * to sample the automasking face set after the stroke has started. + */ + if (set_active_faceset && + *fd.face_set != abs(ss->cache->automasking->settings.initial_face_set)) + { + + float radius = ss->cache->radius; + float pixels = 8; /* TODO: multiply with DPI? */ + radius = pixels * (radius / (float)ss->cache->dyntopo_pixel_radius); + + if (sqrtf(test.dist) < radius) { + ss->cache->automasking->settings.initial_face_set = *fd.face_set; + set_active_faceset = false; + ss->cache->automasking->settings.flags |= BRUSH_AUTOMASKING_FACE_SETS; } - BKE_pbvh_vertex_iter_end; - - if (changed) { - undo::push_node(ob, node, undo::Type::FaceSet); + else { + continue; } } - }); - attribute.finish(); -} -static void do_draw_face_sets_brush_bmesh(Object *ob, - const Brush *brush, - const Span nodes) -{ - SculptSession *ss = ob->sculpt; - const float bstrength = ss->cache->bstrength; - const int cd_offset = ensure_face_sets_bmesh(*ob); - - threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { - const int thread_id = BLI_task_parallel_thread_id(nullptr); - for (PBVHNode *node : nodes.slice(range)) { - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, brush->falloff_shape); - - /* Disable auto-masking code path which rely on an undo step to access original data. - * - * This is because the dynamic topology uses BMesh Log based undo system, which creates a - * single node for the undo step, and its type could be different for the needs of the brush - * undo and the original data access. - * - * For the brushes like Draw the ss->cache->automasking is set to nullptr at the first step - * of the brush, as there is an explicit check there for the brushes which support dynamic - * topology. Do it locally here for the Draw Face Set brush here, to mimic the behavior of - * the other brushes but without marking the brush as supporting dynamic topology. */ - auto_mask::NodeData automask_data = auto_mask::node_begin(*ob, nullptr, *node); - - bool changed = false; - - for (BMFace *f : BKE_pbvh_bmesh_node_faces(node)) { - if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { - continue; - } - - float3 face_center; - BM_face_calc_center_median(f, face_center); - - const BMLoop *l_iter = f->l_first = BM_FACE_FIRST_LOOP(f); - do { - if (!sculpt_brush_test_sq_fn(&test, l_iter->v->co)) { - continue; - } - - BMVert *vert = l_iter->v; - - /* There is no need to update the automasking data as it is disabled above. Additionally, - * there is no access to the PBVHVertexIter as iteration happens over faces. - * - * The full auto-masking support would be very good to be implemented here, so keeping - * the typical code flow for it here for the reference, and ease of looking at what needs - * to be done for such integration. - * - * auto_mask::node_update(automask_data, vd); */ - - const float fade = bstrength * - SCULPT_brush_strength_factor(ss, - brush, - face_center, - sqrtf(test.dist), - f->no, - f->no, - 0.0f, - BKE_pbvh_make_vref(intptr_t(vert)), - thread_id, - &automask_data); - - if (fade <= FACE_SET_BRUSH_MIN_FADE) { - continue; - } - - int &fset = *static_cast(POINTER_OFFSET(f->head.data, cd_offset)); - fset = ss->cache->paint_face_set; - changed = true; - break; - - } while ((l_iter = l_iter->next) != f->l_first); - } - - if (changed) { - undo::push_node(ob, node, undo::Type::FaceSet); + if (have_fset_automasking) { + if (*fd.face_set != ss->cache->automasking->settings.initial_face_set) { + continue; } } - }); + + float fno[3]; + face_normal_get(ob, fd.face, fno); + + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + poly_center, + sqrtf(test.dist), + fno, + fno, + mask, + fd.verts[0], + thread_id, + &automask_data); + + if (fade > 0.05f) { + for (int i = 0; i < fd.verts_num; i++) { + BKE_sculpt_boundary_flag_update(ss, fd.verts[i], true); + } + + *fd.face_set = ss->cache->paint_face_set; + changed = true; + } + } + BKE_pbvh_face_iter_end(fd); + + if (changed) { + BKE_pbvh_vert_tag_update_normal_triangulation(node); + BKE_pbvh_node_mark_rebuild_draw(node); + } } static void do_relax_face_sets_brush_task(Object *ob, @@ -416,8 +270,7 @@ static void do_relax_face_sets_brush_task(Object *ob, } const int thread_id = BLI_task_parallel_thread_id(nullptr); - auto_mask::NodeData automask_data = auto_mask::node_begin( - *ob, ss->cache->automasking.get(), *node); + auto_mask::NodeData automask_data = auto_mask::node_begin(*ob, ss->cache->automasking.get(), *node); BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { auto_mask::node_update(automask_data, vd); @@ -440,18 +293,27 @@ static void do_relax_face_sets_brush_task(Object *ob, thread_id, &automask_data); - smooth::relax_vertex(ss, &vd, fade * bstrength, relax_face_sets, vd.co); + smooth::relax_vertex(ss, &vd, fade * bstrength, SCULPT_BOUNDARY_FACE_SET, vd.co); } BKE_pbvh_vertex_iter_end; } void do_draw_face_sets_brush(Sculpt *sd, Object *ob, Span nodes) { + using namespace blender; SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); BKE_curvemapping_init(brush->curve); + /* Note: face set automasking is fairly involved in this brush. */ + bool have_fset_automasking = ss->cache->automasking && ss->cache->automasking->settings.flags & + BRUSH_AUTOMASKING_FACE_SETS; + /* In invert mode we have to set the automasking face set ourselves. */ + bool set_active_faceset = have_fset_automasking && ss->cache->invert && + ss->cache->automasking->settings.initial_face_set == + ss->cache->paint_face_set; + if (ss->cache->alt_smooth) { SCULPT_boundary_info_ensure(ob); for (int i = 0; i < 4; i++) { @@ -463,60 +325,107 @@ void do_draw_face_sets_brush(Sculpt *sd, Object *ob, Span nodes) } } else { - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - do_draw_face_sets_brush_faces(ob, brush, nodes); - break; - case PBVH_GRIDS: - do_draw_face_sets_brush_grids(ob, brush, nodes); - break; - case PBVH_BMESH: - do_draw_face_sets_brush_bmesh(ob, brush, nodes); - break; - } + threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { + for (const int i : range) { + do_draw_face_sets_brush_task( + ob, brush, have_fset_automasking, set_active_faceset, nodes[i]); + } + }); } } +/* Face Sets Operators */ + +enum eSculptFaceGroupsCreateModes { + SCULPT_FACE_SET_MASKED = 0, + SCULPT_FACE_SET_VISIBLE = 1, + SCULPT_FACE_SET_ALL = 2, + SCULPT_FACE_SET_SELECTION = 3, +}; + +static EnumPropertyItem prop_sculpt_face_set_create_types[] = { + { + SCULPT_FACE_SET_MASKED, + "MASKED", + 0, + "Face Set from Masked", + "Create a new Face Set from the masked faces", + }, + { + SCULPT_FACE_SET_VISIBLE, + "VISIBLE", + 0, + "Face Set from Visible", + "Create a new Face Set from the visible vertices", + }, + { + SCULPT_FACE_SET_ALL, + "ALL", + 0, + "Face Set Full Mesh", + "Create an unique Face Set with all faces in the sculpt", + }, + { + SCULPT_FACE_SET_SELECTION, + "SELECTION", + 0, + "Face Set from Edit Mode Selection", + "Create an Face Set corresponding to the Edit Mode face selection", + }, + {0, nullptr, 0, nullptr, nullptr}, +}; + +int find_next_available_id(Object &ob) +{ + SculptSession *ss = ob.sculpt; + + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_BMESH: { + BKE_sculpt_face_sets_ensure(&ob); + + int fset = 1; + BMFace *f; + BMIter iter; + int cd_fset = ss->attrs.face_set->bmesh_cd_offset; + + BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { + fset = max_ii(fset, BM_ELEM_CD_GET_INT(f, cd_fset) + 1); + } + + return fset; + } + case PBVH_FACES: + case PBVH_GRIDS: + return mesh_find_next_available_id(static_cast(ob.data)); + } + + return 1; +} + static void face_sets_update(Object &object, const Span nodes, const FunctionRef, MutableSpan)> calc_face_sets) { PBVH &pbvh = *object.sculpt->pbvh; + Mesh &mesh = *static_cast(object.data); bke::SpanAttributeWriter face_sets = ensure_face_sets_mesh(object); - struct TLS { - Vector face_indices; - Vector new_face_sets; - }; + Array indices(mesh.faces_num); + for (int i = 0; i < mesh.faces_num; i++) { + indices[i] = i; + } - threading::EnumerableThreadSpecific all_tls; - threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { - TLS &tls = all_tls.local(); - for (PBVHNode *node : nodes.slice(range)) { - const Span faces = - BKE_pbvh_type(&pbvh) == PBVH_FACES ? - bke::pbvh::node_face_indices_calc_mesh(pbvh, *node, tls.face_indices) : - bke::pbvh::node_face_indices_calc_grids(pbvh, *node, tls.face_indices); + for (int i = 0; i < mesh.faces_num; i++) { + calc_face_sets(indices, face_sets.span); + } - tls.new_face_sets.reinitialize(faces.size()); - MutableSpan new_face_sets = tls.new_face_sets; - array_utils::gather(face_sets.span.as_span(), faces, new_face_sets); - calc_face_sets(faces, new_face_sets); - if (!array_utils::indexed_data_equal(face_sets.span, faces, new_face_sets)) { - continue; - } - - undo::push_node(&object, node, undo::Type::FaceSet); - array_utils::scatter(new_face_sets.as_span(), faces, face_sets.span); - BKE_pbvh_node_mark_update_face_sets(node); - } - }); + for (PBVHNode *node : nodes) { + BKE_pbvh_node_mark_update_face_sets(node); + } face_sets.finish(); } -/* Face Sets Operators */ - enum class CreateMode { Masked = 0, Visible = 1, @@ -538,10 +447,7 @@ static void clear_face_sets(Object &object, const Span nodes) threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { Vector &face_indices = all_face_indices.local(); for (PBVHNode *node : nodes.slice(range)) { - const Span faces = - BKE_pbvh_type(&pbvh) == PBVH_FACES ? - bke::pbvh::node_face_indices_calc_mesh(pbvh, *node, face_indices) : - bke::pbvh::node_face_indices_calc_grids(pbvh, *node, face_indices); + const Span faces = bke::pbvh::node_face_indices_calc_mesh(pbvh, *node, face_indices); if (std::any_of(faces.begin(), faces.end(), [&](const int face) { return face_sets[face] != default_face_set; })) @@ -552,6 +458,7 @@ static void clear_face_sets(Object &object, const Span nodes) } }); attributes.remove(".sculpt_face_set"); + BKE_sculptsession_sync_attributes(&object, &mesh, false); } static int sculpt_face_set_create_exec(bContext *C, wmOperator *op) @@ -559,7 +466,6 @@ static int sculpt_face_set_create_exec(bContext *C, wmOperator *op) Object &object = *CTX_data_active_object(C); SculptSession &ss = *object.sculpt; Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(C); - const CreateMode mode = CreateMode(RNA_enum_get(op->ptr, "mode")); const View3D *v3d = CTX_wm_view3d(C); @@ -568,16 +474,17 @@ static int sculpt_face_set_create_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - if (BKE_pbvh_type(ss.pbvh) == PBVH_BMESH) { - /* Dyntopo not supported. */ - return OPERATOR_CANCELLED; + BKE_sculpt_update_object_for_edit(&depsgraph, &object, false); + + bool is_bmesh = BKE_pbvh_type(ss.pbvh) == PBVH_BMESH; + if (is_bmesh) { + /* Run operator on original mesh and flush back to bmesh. */ + BKE_sculptsession_bm_to_me_for_render(&object); } Mesh &mesh = *static_cast(object.data); const bke::AttributeAccessor attributes = mesh.attributes(); - BKE_sculpt_update_object_for_edit(&depsgraph, &object, false); - undo::push_begin(&object, op); const int next_face_set = find_next_available_id(object); @@ -657,6 +564,19 @@ static int sculpt_face_set_create_exec(bContext *C, wmOperator *op) } } + if (is_bmesh) { + /* Load face sets back into bmesh. */ + BMFace *f; + BMIter iter; + int i; + int cd_fset = CustomData_get_offset_named(&ss.bm->pdata, CD_PROP_INT32, ".sculpt_face_set"); + const int *face_sets = static_cast( + CustomData_get_layer_named(&mesh.vert_data, CD_PROP_INT32, ".sculpt_face_set")); + + BM_ITER_MESH_INDEX (f, &iter, ss.bm, BM_FACES_OF_MESH, i) { + BM_ELEM_CD_SET_INT(f, cd_fset, face_sets[i]); + } + } undo::push_end(&object); SCULPT_tag_update_overlays(C); @@ -666,39 +586,19 @@ static int sculpt_face_set_create_exec(bContext *C, wmOperator *op) void SCULPT_OT_face_sets_create(wmOperatorType *ot) { + /* identifiers */ ot->name = "Create Face Set"; ot->idname = "SCULPT_OT_face_sets_create"; ot->description = "Create a new Face Set"; + /* api callbacks */ ot->exec = sculpt_face_set_create_exec; ot->poll = SCULPT_mode_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - static EnumPropertyItem modes[] = { - {int(CreateMode::Masked), - "MASKED", - 0, - "Face Set from Masked", - "Create a new Face Set from the masked faces"}, - {int(CreateMode::Visible), - "VISIBLE", - 0, - "Face Set from Visible", - "Create a new Face Set from the visible vertices"}, - {int(CreateMode::All), - "ALL", - 0, - "Face Set Full Mesh", - "Create an unique Face Set with all faces in the sculpt"}, - {int(CreateMode::Selection), - "SELECTION", - 0, - "Face Set from Edit Mode Selection", - "Create an Face Set corresponding to the Edit Mode face selection"}, - {0, nullptr, 0, nullptr, nullptr}, - }; - RNA_def_enum(ot->srna, "mode", modes, int(CreateMode::Masked), "Mode", ""); + RNA_def_enum( + ot->srna, "mode", prop_sculpt_face_set_create_types, SCULPT_FACE_SET_MASKED, "Mode", ""); } enum class InitMode { @@ -712,16 +612,80 @@ enum class InitMode { FaceSetBoundaries = 8, }; -using FaceSetsFloodFillFn = FunctionRef; +static EnumPropertyItem prop_sculpt_face_sets_init_types[] = { + { + int(InitMode::LooseParts), + "LOOSE_PARTS", + 0, + "Face Sets from Loose Parts", + "Create a Face Set per loose part in the mesh", + }, + { + int(InitMode::Materials), + "MATERIALS", + 0, + "Face Sets from Material Slots", + "Create a Face Set per Material Slot", + }, + { + int(InitMode::Normals), + "NORMALS", + 0, + "Face Sets from Mesh Normals", + "Create Face Sets for Faces that have similar normal", + }, + { + int(InitMode::UVSeams), + "UV_SEAMS", + 0, + "Face Sets from UV Seams", + "Create Face Sets using UV Seams as boundaries", + }, + { + int(InitMode::Creases), + "CREASES", + 0, + "Face Sets from Edge Creases", + "Create Face Sets using Edge Creases as boundaries", + }, + { + int(InitMode::BevelWeight), + "BEVEL_WEIGHT", + 0, + "Face Sets from Bevel Weight", + "Create Face Sets using Bevel Weights as boundaries", + }, + { + int(InitMode::SharpEdges), + "SHARP_EDGES", + 0, + "Face Sets from Sharp Edges", + "Create Face Sets using Sharp Edges as boundaries", + }, + + { + int(InitMode::FaceSetBoundaries), + "FACE_SET_BOUNDARIES", + 0, + "Face Sets from Face Set Boundaries", + "Create a Face Set per isolated Face Set", + }, + + {0, nullptr, 0, nullptr, nullptr}, +}; + +using FaceSetsFloodFillFn = blender::FunctionRef; static void sculpt_face_sets_init_flood_fill(Object *ob, const FaceSetsFloodFillFn &test_fn) { + using namespace blender; SculptSession *ss = ob->sculpt; Mesh *mesh = static_cast(ob->data); BitVector<> visited_faces(mesh->faces_num, false); - bke::SpanAttributeWriter face_sets = ensure_face_sets_mesh(*ob); + int *face_sets = static_cast(CustomData_get_layer_named_for_write( + &mesh->face_data, CD_PROP_INT32, SCULPT_ATTRIBUTE_NAME(face_set), mesh->faces_num)); const Span edges = mesh->edges(); const OffsetIndices faces = mesh->faces(); @@ -740,7 +704,7 @@ static void sculpt_face_sets_init_flood_fill(Object *ob, const FaceSetsFloodFill } std::queue queue; - face_sets.span[i] = next_face_set; + face_sets[i] = next_face_set; visited_faces[i].set(true); queue.push(i); @@ -760,7 +724,7 @@ static void sculpt_face_sets_init_flood_fill(Object *ob, const FaceSetsFloodFill continue; } - face_sets.span[neighbor_i] = next_face_set; + face_sets[neighbor_i] = next_face_set; visited_faces[neighbor_i].set(true); queue.push(neighbor_i); } @@ -769,22 +733,27 @@ static void sculpt_face_sets_init_flood_fill(Object *ob, const FaceSetsFloodFill next_face_set += 1; } - - face_sets.finish(); } -Array duplicate_face_sets(const Mesh &mesh) +static void sculpt_face_sets_init_loop(Object *ob, const InitMode mode) { - const bke::AttributeAccessor attributes = mesh.attributes(); - const VArray attribute = *attributes.lookup_or_default( - ".sculpt_face_set", bke::AttrDomain::Face, 0); - Array face_sets(attribute.size()); - array_utils::copy(attribute, face_sets.as_mutable_span()); - return face_sets; + using namespace blender; + Mesh *mesh = static_cast(ob->data); + SculptSession *ss = ob->sculpt; + + if (mode == InitMode::Materials) { + const bke::AttributeAccessor attributes = mesh->attributes(); + const VArraySpan material_indices = *attributes.lookup_or_default( + "material_index", bke::AttrDomain::Face, 0); + for (const int i : IndexRange(mesh->faces_num)) { + ss->face_sets[i] = material_indices[i] + 1; + } + } } static int sculpt_face_set_init_exec(bContext *C, wmOperator *op) { + using namespace blender; Object *ob = CTX_data_active_object(C); SculptSession *ss = ob->sculpt; Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); @@ -799,28 +768,39 @@ static int sculpt_face_set_init_exec(bContext *C, wmOperator *op) BKE_sculpt_update_object_for_edit(depsgraph, ob, false); - /* Dyntopo not supported. */ - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - return OPERATOR_CANCELLED; - } - PBVH *pbvh = ob->sculpt->pbvh; - Vector nodes = bke::pbvh::search_gather(pbvh, {}); + Vector nodes = blender::bke::pbvh::search_gather(pbvh, {}); if (nodes.is_empty()) { return OPERATOR_CANCELLED; } + const float threshold = RNA_float_get(op->ptr, "threshold"); + + Mesh *mesh = static_cast(ob->data); + BKE_sculpt_face_sets_ensure(ob); + undo::push_begin(ob, op); for (PBVHNode *node : nodes) { undo::push_node(ob, node, undo::Type::FaceSet); } - const float threshold = RNA_float_get(op->ptr, "threshold"); + /* Flush bmesh to base mesh. */ + if (ss->bm) { + BKE_sculptsession_bm_to_me_for_render(ob); + + if (!ss->edge_to_face_map.is_empty()) { + ss->edge_to_face_map = {}; + ss->edge_to_face_indices = {}; + ss->edge_to_face_offsets = {}; + } + + if (!ss->vert_to_face_map.is_empty()) { + ss->vert_to_face_map = {}; + } + } - Mesh *mesh = static_cast(ob->data); const bke::AttributeAccessor attributes = mesh->attributes(); - switch (mode) { case InitMode::LooseParts: { const VArray hide_poly = *attributes.lookup_or_default( @@ -832,13 +812,7 @@ static int sculpt_face_set_init_exec(bContext *C, wmOperator *op) break; } case InitMode::Materials: { - bke::SpanAttributeWriter face_sets = ensure_face_sets_mesh(*ob); - const VArraySpan material_indices = *attributes.lookup_or_default( - "material_index", bke::AttrDomain::Face, 0); - for (const int i : IndexRange(mesh->faces_num)) { - face_sets.span[i] = material_indices[i] + 1; - } - face_sets.finish(); + sculpt_face_sets_init_loop(ob, InitMode::Materials); break; } case InitMode::Normals: { @@ -859,11 +833,11 @@ static int sculpt_face_set_init_exec(bContext *C, wmOperator *op) break; } case InitMode::Creases: { - const VArraySpan creases = *attributes.lookup_or_default( - "crease_edge", bke::AttrDomain::Edge, 0.0f); + const float *creases = static_cast( + CustomData_get_layer_named(&mesh->edge_data, CD_PROP_FLOAT, "crease_edge")); sculpt_face_sets_init_flood_fill( ob, [&](const int /*from_face*/, const int edge, const int /*to_face*/) -> bool { - return creases[edge] < threshold; + return creases ? creases[edge] < threshold : true; }); break; } @@ -877,16 +851,16 @@ static int sculpt_face_set_init_exec(bContext *C, wmOperator *op) break; } case InitMode::BevelWeight: { - const VArraySpan bevel_weights = *attributes.lookup_or_default( - "bevel_weight_edge", bke::AttrDomain::Edge, 0.0f); + const float *bevel_weights = static_cast( + CustomData_get_layer_named(&mesh->edge_data, CD_PROP_FLOAT, "bevel_weight_edge")); sculpt_face_sets_init_flood_fill( ob, [&](const int /*from_face*/, const int edge, const int /*to_face*/) -> bool { - return bevel_weights[edge] < threshold; + return bevel_weights ? bevel_weights[edge] < threshold : true; }); break; } case InitMode::FaceSetBoundaries: { - Array face_sets_copy = duplicate_face_sets(*mesh); + Array face_sets_copy(Span(ss->face_sets, mesh->faces_num)); sculpt_face_sets_init_flood_fill( ob, [&](const int from_face, const int /*edge*/, const int to_face) -> bool { return face_sets_copy[from_face] == face_sets_copy[to_face]; @@ -897,70 +871,92 @@ static int sculpt_face_set_init_exec(bContext *C, wmOperator *op) undo::push_end(ob); - for (PBVHNode *node : nodes) { - BKE_pbvh_node_mark_redraw(node); + if (ss->bm) { + SCULPT_vertex_random_access_ensure(ss); + SCULPT_face_random_access_ensure(ss); + BKE_sculpt_face_sets_ensure(ob); + + int cd_fset = ss->attrs.face_set->bmesh_cd_offset; + const int *face_sets = static_cast(CustomData_get_layer_named( + &mesh->face_data, CD_PROP_INT32, SCULPT_ATTRIBUTE_NAME(face_set))); + + for (int i = 0; i < mesh->faces_num; i++) { + BMFace *f = ss->bm->ftable[i]; + BM_ELEM_CD_SET_INT(f, cd_fset, face_sets[i]); + } } + int verts_num = SCULPT_vertex_count_get(ob->sculpt); + for (int i : IndexRange(verts_num)) { + BKE_sculpt_boundary_flag_update(ob->sculpt, BKE_pbvh_index_to_vertex(ss->pbvh, i), true); + } + + /* Sync face sets visibility and vertex visibility as now all Face Sets are visible. */ + hide::sync_all_from_faces(*ob); + + for (PBVHNode *node : nodes) { + BKE_pbvh_node_mark_update_visibility(node); + } + + bke::pbvh::update_visibility(*ss->pbvh); + SCULPT_tag_update_overlays(C); return OPERATOR_FINISHED; } +Array duplicate_face_sets(Object &object) +{ + SculptSession *ss = object.sculpt; + + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + case PBVH_GRIDS: { + Mesh &mesh = *BKE_object_get_original_mesh(&object); + const bke::AttributeAccessor attributes = mesh.attributes(); + const VArray attribute = *attributes.lookup_or_default( + ".sculpt_face_set", bke::AttrDomain::Face, 0); + Array face_sets(attribute.size()); + array_utils::copy(attribute, face_sets.as_mutable_span()); + return face_sets; + } + case PBVH_BMESH: { + BMesh *bm = ss->bm; + Array face_sets(bm->totface); + int cd_fset = CustomData_get_offset_named(&bm->pdata, CD_PROP_INT32, ".sculpt_face_set"); + + if (cd_fset == -1) { + return face_sets; + } + + BMIter iter; + BMFace *f; + int i = 0; + + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + face_sets[i++] = BM_ELEM_CD_GET_INT(f, cd_fset); + } + } + } + + return Array(ss->totfaces); +} + void SCULPT_OT_face_sets_init(wmOperatorType *ot) { + /* identifiers */ ot->name = "Init Face Sets"; ot->idname = "SCULPT_OT_face_sets_init"; ot->description = "Initializes all Face Sets in the mesh"; + /* api callbacks */ ot->exec = sculpt_face_set_init_exec; ot->poll = SCULPT_mode_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - static EnumPropertyItem modes[] = { - {int(InitMode::LooseParts), - "LOOSE_PARTS", - 0, - "Face Sets from Loose Parts", - "Create a Face Set per loose part in the mesh"}, - {int(InitMode::Materials), - "MATERIALS", - 0, - "Face Sets from Material Slots", - "Create a Face Set per Material Slot"}, - {int(InitMode::Normals), - "NORMALS", - 0, - "Face Sets from Mesh Normals", - "Create Face Sets for Faces that have similar normal"}, - {int(InitMode::UVSeams), - "UV_SEAMS", - 0, - "Face Sets from UV Seams", - "Create Face Sets using UV Seams as boundaries"}, - {int(InitMode::Creases), - "CREASES", - 0, - "Face Sets from Edge Creases", - "Create Face Sets using Edge Creases as boundaries"}, - {int(InitMode::BevelWeight), - "BEVEL_WEIGHT", - 0, - "Face Sets from Bevel Weight", - "Create Face Sets using Bevel Weights as boundaries"}, - {int(InitMode::SharpEdges), - "SHARP_EDGES", - 0, - "Face Sets from Sharp Edges", - "Create Face Sets using Sharp Edges as boundaries"}, - {int(InitMode::FaceSetBoundaries), - "FACE_SET_BOUNDARIES", - 0, - "Face Sets from Face Set Boundaries", - "Create a Face Set per isolated Face Set"}, - {0, nullptr, 0, nullptr, nullptr}, - }; - RNA_def_enum(ot->srna, "mode", modes, int(InitMode::LooseParts), "Mode", ""); + RNA_def_enum( + ot->srna, "mode", prop_sculpt_face_sets_init_types, SCULPT_FACE_SET_MASKED, "Mode", ""); RNA_def_float( ot->srna, "threshold", @@ -979,159 +975,155 @@ enum class VisibilityMode { HideActive = 2, }; -static void face_hide_update(Object &object, - const Span nodes, - const FunctionRef, MutableSpan)> calc_hide) -{ - PBVH &pbvh = *object.sculpt->pbvh; - Mesh &mesh = *static_cast(object.data); - bke::MutableAttributeAccessor attributes = mesh.attributes_for_write(); - bke::SpanAttributeWriter hide_poly = attributes.lookup_or_add_for_write_span( - ".hide_poly", bke::AttrDomain::Face); - - struct TLS { - Vector face_indices; - Vector new_hide; - }; - - bool any_changed = false; - threading::EnumerableThreadSpecific all_tls; - threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { - TLS &tls = all_tls.local(); - for (PBVHNode *node : nodes.slice(range)) { - const Span faces = - BKE_pbvh_type(&pbvh) == PBVH_FACES ? - bke::pbvh::node_face_indices_calc_mesh(pbvh, *node, tls.face_indices) : - bke::pbvh::node_face_indices_calc_grids(pbvh, *node, tls.face_indices); - - tls.new_hide.reinitialize(faces.size()); - MutableSpan new_hide = tls.new_hide; - array_utils::gather(hide_poly.span.as_span(), faces, new_hide); - calc_hide(faces, new_hide); - if (!array_utils::indexed_data_equal(hide_poly.span, faces, new_hide)) { - continue; - } - - any_changed = true; - undo::push_node(&object, node, undo::Type::HideFace); - array_utils::scatter(new_hide.as_span(), faces, hide_poly.span); - BKE_pbvh_node_mark_update_visibility(node); - } - }); - - hide_poly.finish(); - if (any_changed) { - hide::sync_all_from_faces(object); - } -} - -static void show_all(Depsgraph &depsgraph, Object &object, const Span nodes) -{ - switch (BKE_pbvh_type(object.sculpt->pbvh)) { - case PBVH_FACES: - hide::mesh_show_all(object, nodes); - break; - case PBVH_GRIDS: - hide::grids_show_all(depsgraph, object, nodes); - break; - case PBVH_BMESH: - BLI_assert_unreachable(); - break; - } -} +static EnumPropertyItem prop_sculpt_face_sets_change_visibility_types[] = { + { + int(VisibilityMode::Toggle), + "TOGGLE", + 0, + "Toggle Visibility", + "Hide all Face Sets except for the active one", + }, + { + int(VisibilityMode::ShowActive), + "SHOW_ACTIVE", + 0, + "Show Active Face Set", + "Show Active Face Set", + }, + { + int(VisibilityMode::HideActive), + "HIDE_ACTIVE", + 0, + "Hide Active Face Sets", + "Hide Active Face Sets", + }, + {0, nullptr, 0, nullptr, nullptr}, +}; static int sculpt_face_set_change_visibility_exec(bContext *C, wmOperator *op) { - Object &object = *CTX_data_active_object(C); - SculptSession *ss = object.sculpt; - Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(C); + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - Mesh *mesh = BKE_object_get_original_mesh(&object); - BKE_sculpt_update_object_for_edit(&depsgraph, &object, false); - - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - /* Not supported for dyntopo. There is no active face. */ + const View3D *v3d = CTX_wm_view3d(C); + const Base *base = CTX_data_active_base(C); + if (!BKE_base_is_visible(v3d, base)) { return OPERATOR_CANCELLED; } + BKE_sculpt_update_object_for_edit(depsgraph, ob, false); + SCULPT_vertex_random_access_ensure(ss); + SCULPT_face_random_access_ensure(ss); + const VisibilityMode mode = VisibilityMode(RNA_enum_get(op->ptr, "mode")); + const int tot_vert = SCULPT_vertex_count_get(ss); + + PBVH *pbvh = ob->sculpt->pbvh; + Vector nodes = blender::bke::pbvh::search_gather(pbvh, {}); + + if (nodes.is_empty()) { + return OPERATOR_CANCELLED; + } + const int active_face_set = active_face_set_get(ss); - undo::push_begin(&object, op); - - PBVH *pbvh = object.sculpt->pbvh; - Vector nodes = bke::pbvh::search_gather(pbvh, {}); - - const bke::AttributeAccessor attributes = mesh->attributes(); - const VArraySpan hide_poly = *attributes.lookup(".hide_poly", bke::AttrDomain::Face); - const VArraySpan face_sets = *attributes.lookup(".sculpt_face_set", - bke::AttrDomain::Face); + undo::push_begin(ob, op); + for (PBVHNode *node : nodes) { + undo::push_node(ob, node, undo::Type::HideFace); + } switch (mode) { case VisibilityMode::Toggle: { - if (hide_poly.contains(true) || face_sets.is_empty()) { - show_all(depsgraph, object, nodes); + bool hidden_vertex = false; + + /* This can fail with regular meshes with non-manifold geometry as the visibility state can't + * be synced from face sets to non-manifold vertices. */ + if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { + for (int i = 0; i < tot_vert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + if (!hide::vert_visible_get(ss, vertex)) { + hidden_vertex = true; + break; + } + } + } + + if (ss->attrs.hide_poly) { + for (int i = 0; i < ss->totfaces; i++) { + PBVHFaceRef face = BKE_pbvh_index_to_face(ss->pbvh, i); + if (SCULPT_face_is_hidden(ss, face)) { + hidden_vertex = true; + break; + } + } + } + + BKE_sculpt_hide_poly_ensure(ob); + + if (hidden_vertex) { + face_set::visibility_all_set(ob, true); } else { - face_hide_update(object, nodes, [&](const Span faces, MutableSpan hide) { - for (const int i : hide.index_range()) { - hide[i] = face_sets[faces[i]] != active_face_set; - } - }); + if (ss->attrs.face_set) { + face_set::visibility_all_set(ob, false); + hide::face_set(ss, active_face_set, true); + } + else { + face_set::visibility_all_set(ob, true); + } } break; } case VisibilityMode::ShowActive: - if (face_sets.is_empty()) { - show_all(depsgraph, object, nodes); + BKE_sculpt_hide_poly_ensure(ob); + + if (ss->attrs.face_set) { + face_set::visibility_all_set(ob, false); + hide::face_set(ss, active_face_set, true); } else { - face_hide_update(object, nodes, [&](const Span faces, MutableSpan hide) { - for (const int i : hide.index_range()) { - if (face_sets[faces[i]] == active_face_set) { - hide[i] = false; - } - } - }); + hide::face_set(ss, active_face_set, true); } break; case VisibilityMode::HideActive: - if (face_sets.is_empty()) { - face_hide_update(object, nodes, [&](const Span /*faces*/, MutableSpan hide) { - hide.fill(true); - }); + BKE_sculpt_hide_poly_ensure(ob); + + if (ss->attrs.face_set) { + hide::face_set(ss, active_face_set, false); } else { - face_hide_update(object, nodes, [&](const Span faces, MutableSpan hide) { - for (const int i : hide.index_range()) { - if (face_sets[faces[i]] == active_face_set) { - hide[i] = true; - } - } - }); + face_set::visibility_all_set(ob, false); } + break; } /* For modes that use the cursor active vertex, update the rotation origin for viewport - * navigation. */ + * navigation. + */ if (ELEM(mode, VisibilityMode::Toggle, VisibilityMode::ShowActive)) { UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings; float location[3]; copy_v3_v3(location, SCULPT_active_vertex_co_get(ss)); - mul_m4_v3(object.object_to_world().ptr(), location); + mul_m4_v3(ob->object_to_world().ptr(), location); copy_v3_v3(ups->average_stroke_accum, location); ups->average_stroke_counter = 1; ups->last_stroke_valid = true; } - undo::push_end(&object); + /* Sync face sets visibility and vertex visibility. */ + hide::sync_all_from_faces(*ob); + + undo::push_end(ob); + for (PBVHNode *node : nodes) { + BKE_pbvh_node_mark_update_visibility(node); + } bke::pbvh::update_visibility(*ss->pbvh); - BKE_sculpt_hide_poly_pointer_update(object); - SCULPT_topology_islands_invalidate(object.sculpt); - hide::tag_update_visibility(*C); + SCULPT_tag_update_overlays(C); return OPERATOR_FINISHED; } @@ -1143,12 +1135,6 @@ static int sculpt_face_set_change_visibility_invoke(bContext *C, Object *ob = CTX_data_active_object(C); SculptSession *ss = ob->sculpt; - const View3D *v3d = CTX_wm_view3d(C); - const Base *base = CTX_data_active_base(C); - if (!BKE_base_is_visible(v3d, base)) { - return OPERATOR_CANCELLED; - } - /* Update the active vertex and Face Set using the cursor position to avoid relying on the paint * cursor updates. */ SculptCursorGeometryInfo sgi; @@ -1161,39 +1147,29 @@ static int sculpt_face_set_change_visibility_invoke(bContext *C, void SCULPT_OT_face_set_change_visibility(wmOperatorType *ot) { + /* Identifiers. */ ot->name = "Face Sets Visibility"; ot->idname = "SCULPT_OT_face_set_change_visibility"; ot->description = "Change the visibility of the Face Sets of the sculpt"; + /* Api callbacks. */ ot->exec = sculpt_face_set_change_visibility_exec; ot->invoke = sculpt_face_set_change_visibility_invoke; ot->poll = SCULPT_mode_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; - static EnumPropertyItem modes[] = { - {int(VisibilityMode::Toggle), - "TOGGLE", - 0, - "Toggle Visibility", - "Hide all Face Sets except for the active one"}, - {int(VisibilityMode::ShowActive), - "SHOW_ACTIVE", - 0, - "Show Active Face Set", - "Show Active Face Set"}, - {int(VisibilityMode::HideActive), - "HIDE_ACTIVE", - 0, - "Hide Active Face Sets", - "Hide Active Face Sets"}, - {0, nullptr, 0, nullptr, nullptr}, - }; - RNA_def_enum(ot->srna, "mode", modes, int(VisibilityMode::Toggle), "Mode", ""); + RNA_def_enum(ot->srna, + "mode", + prop_sculpt_face_sets_change_visibility_types, + int(VisibilityMode::Toggle), + "Mode", + ""); } static int sculpt_face_sets_randomize_colors_exec(bContext *C, wmOperator * /*op*/) { + Object *ob = CTX_data_active_object(C); SculptSession *ss = ob->sculpt; @@ -1203,27 +1179,27 @@ static int sculpt_face_sets_randomize_colors_exec(bContext *C, wmOperator * /*op return OPERATOR_CANCELLED; } - /* Dyntopo not supported. */ - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + if (!ss->attrs.face_set) { return OPERATOR_CANCELLED; } + SCULPT_face_random_access_ensure(ss); + PBVH *pbvh = ob->sculpt->pbvh; Mesh *mesh = static_cast(ob->data); - const bke::AttributeAccessor attributes = mesh->attributes(); - - if (!attributes.contains(".sculpt_face_set")) { - return OPERATOR_CANCELLED; - } - - const VArray face_sets = *attributes.lookup(".sculpt_face_set", bke::AttrDomain::Face); - const int random_index = clamp_i( - ss->totfaces * BLI_hash_int_01(mesh->face_sets_color_seed), 0, max_ii(0, ss->totfaces - 1)); - mesh->face_sets_color_default = face_sets[random_index]; mesh->face_sets_color_seed += 1; + if (ss->attrs.face_set) { + const int random_index = clamp_i(ss->totfaces * BLI_hash_int_01(mesh->face_sets_color_seed), + 0, + max_ii(0, ss->totfaces - 1)); + PBVHFaceRef face = BKE_pbvh_index_to_face(ss->pbvh, random_index); - Vector nodes = bke::pbvh::search_gather(pbvh, {}); + mesh->face_sets_color_default = blender::bke::paint::face_attr_get(face, + ss->attrs.face_set); + } + + Vector nodes = blender::bke::pbvh::search_gather(pbvh, {}); for (PBVHNode *node : nodes) { BKE_pbvh_node_mark_redraw(node); } @@ -1235,10 +1211,12 @@ static int sculpt_face_sets_randomize_colors_exec(bContext *C, wmOperator * /*op void SCULPT_OT_face_sets_randomize_colors(wmOperatorType *ot) { + /* Identifiers. */ ot->name = "Randomize Face Sets Colors"; ot->idname = "SCULPT_OT_face_sets_randomize_colors"; ot->description = "Generates a new set of random colors to render the Face Sets in the viewport"; + /* Api callbacks. */ ot->exec = sculpt_face_sets_randomize_colors_exec; ot->poll = SCULPT_mode_poll; @@ -1253,6 +1231,61 @@ enum class EditMode { FairTangency = 4, }; +static void sculpt_face_set_grow_shrink_bmesh(Object *ob, + SculptSession *ss, + const Array prev_face_sets, + const int active_face_set_id, + const bool modify_hidden, + bool grow) +{ + using namespace blender; + + Mesh *mesh = BKE_mesh_from_object(ob); + const OffsetIndices faces = mesh->faces(); + const Span corner_verts = mesh->corner_verts(); + + Vector modified_faces; + + for (int face_i = 0; face_i < ss->totfaces; face_i++) { + PBVHFaceRef face = BKE_pbvh_index_to_face(ss->pbvh, face_i); + + if ((!modify_hidden && SCULPT_face_is_hidden(ss, face)) || + prev_face_sets[face_i] != active_face_set_id) + { + continue; + } + + BMFace *f = reinterpret_cast(face.i); + BMLoop *l = f->l_first; + BMIter iter; + BMFace *f2; + + do { + BM_ITER_ELEM (f2, &iter, l->v, BM_FACES_OF_VERT) { + if (f2 == f || (!modify_hidden && BM_elem_flag_test(f2, BM_ELEM_HIDDEN))) { + continue; + } + + PBVHFaceRef face2 = {reinterpret_cast(f2)}; + int face2_i = BKE_pbvh_face_to_index(ss->pbvh, face2); + + if (grow) { + SCULPT_face_set_set(ss, face2, active_face_set_id); + modified_faces.append(face2); + } + else if (prev_face_sets[face2_i] != active_face_set_id) { + SCULPT_face_set_set(ss, face, prev_face_sets[face2_i]); + modified_faces.append(face); + } + } + } while ((l = l->next) != f->l_first); + } + + for (PBVHFaceRef face : modified_faces) { + face_mark_boundary_update(ss, face); + } +} + static void sculpt_face_set_grow_shrink(Object &object, const EditMode mode, const int active_face_set_id, @@ -1260,13 +1293,20 @@ static void sculpt_face_set_grow_shrink(Object &object, wmOperator *op) { SculptSession &ss = *object.sculpt; + Array prev_face_sets = duplicate_face_sets(object); + + if (ss.bm) { + sculpt_face_set_grow_shrink_bmesh( + &object, &ss, prev_face_sets, active_face_set_id, modify_hidden, mode == EditMode::Grow); + return; + } + Mesh &mesh = *static_cast(object.data); const OffsetIndices faces = mesh.faces(); const Span corner_verts = mesh.corner_verts(); const GroupedSpan vert_to_face_map = ss.vert_to_face_map; const bke::AttributeAccessor attributes = mesh.attributes(); const VArraySpan hide_poly = *attributes.lookup(".hide_poly", bke::AttrDomain::Face); - Array prev_face_sets = duplicate_face_sets(mesh); undo::push_begin(&object, op); @@ -1354,20 +1394,24 @@ static void sculpt_face_set_delete_geometry(Object *ob, const bool modify_hidden) { Mesh &mesh = *static_cast(ob->data); + SculptSession *ss = ob->sculpt; const bke::AttributeAccessor attributes = mesh.attributes(); const VArraySpan hide_poly = *attributes.lookup(".hide_poly", bke::AttrDomain::Face); const VArraySpan face_sets = *attributes.lookup(".sculpt_face_set", bke::AttrDomain::Face); + BMesh *bm = ss->bm; - const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(&mesh); - BMeshCreateParams create_params{}; - create_params.use_toolflags = true; - BMesh *bm = BM_mesh_create(&allocsize, &create_params); + if (!bm) { + const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(&mesh); + BMeshCreateParams create_params{}; + create_params.use_toolflags = true; + bm = BM_mesh_create(&allocsize, &create_params); - BMeshFromMeshParams convert_params{}; - convert_params.calc_vert_normal = true; - convert_params.calc_face_normal = true; - BM_mesh_bm_from_me(bm, &mesh, &convert_params); + BMeshFromMeshParams convert_params{}; + convert_params.calc_vert_normal = true; + convert_params.calc_face_normal = true; + BM_mesh_bm_from_me(bm, &mesh, &convert_params); + } BM_mesh_elem_table_init(bm, BM_FACE); BM_mesh_elem_table_ensure(bm, BM_FACE); @@ -1384,11 +1428,16 @@ static void sculpt_face_set_delete_geometry(Object *ob, BM_mesh_delete_hflag_context(bm, BM_ELEM_TAG, DEL_FACES); BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false); - BMeshToMeshParams bmesh_to_mesh_params{}; - bmesh_to_mesh_params.calc_object_remap = false; - BM_mesh_bm_to_me(nullptr, bm, &mesh, &bmesh_to_mesh_params); + if (!ss->bm) { + BMeshToMeshParams bmesh_to_mesh_params{}; + bmesh_to_mesh_params.calc_object_remap = false; + BM_mesh_bm_to_me(nullptr, bm, &mesh, &bmesh_to_mesh_params); - BM_mesh_free(bm); + BM_mesh_free(bm); + } + else { + SCULPT_pbvh_clear(ob); + } } static void sculpt_face_set_edit_fair_face_set(Object *ob, @@ -1401,6 +1450,7 @@ static void sculpt_face_set_edit_fair_face_set(Object *ob, Mesh *mesh = static_cast(ob->data); Vector orig_positions; + Vector positions; Vector fair_verts; orig_positions.resize(totvert); @@ -1412,17 +1462,28 @@ static void sculpt_face_set_edit_fair_face_set(Object *ob, PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); orig_positions[i] = SCULPT_vertex_co_get(ss, vertex); - fair_verts[i] = !SCULPT_vertex_is_boundary(ss, vertex) && + positions[i] = orig_positions[i]; + fair_verts[i] = !SCULPT_vertex_is_boundary(ss, vertex, SCULPT_BOUNDARY_FACE_SET) && vert_has_face_set(ss, vertex, active_face_set_id) && vert_has_unique_face_set(ss, vertex); } - MutableSpan positions = SCULPT_mesh_deformed_positions_get(ss); + MutableSpan mesh_positions = SCULPT_mesh_deformed_positions_get(ss); BKE_mesh_prefair_and_fair_verts(mesh, positions, fair_verts.data(), fair_order); + bool has_bmesh = ss->bm; + for (int i = 0; i < totvert; i++) { if (fair_verts[i]) { interp_v3_v3v3(positions[i], orig_positions[i], positions[i], strength); + + if (has_bmesh) { + BMVert *v = BM_vert_at_index(ss->bm, i); + copy_v3_v3(v->co, positions[i]); + } + else { + mesh_positions[i] = positions[i]; + } } } } @@ -1431,11 +1492,6 @@ static bool sculpt_face_set_edit_is_operation_valid(const Object &object, const EditMode mode, const bool modify_hidden) { - if (BKE_pbvh_type(object.sculpt->pbvh) == PBVH_BMESH) { - /* Dyntopo is not supported. */ - return false; - } - if (mode == EditMode::DeleteGeometry) { if (BKE_pbvh_type(object.sculpt->pbvh) == PBVH_GRIDS) { /* Modification of base mesh geometry requires special remapping of multi-resolution @@ -1535,6 +1591,11 @@ static bool sculpt_face_set_edit_init(bContext *C, wmOperator *op) BKE_sculpt_update_object_for_edit(depsgraph, ob, false); + if (ob->sculpt->bm) { + /* Load bmesh back into original mesh. */ + BKE_sculptsession_bm_to_me_for_render(ob); + } + return true; } @@ -1652,6 +1713,65 @@ void SCULPT_OT_face_sets_edit(wmOperatorType *ot) "Apply the edit operation to hidden geometry"); } +static int sculpt_face_sets_invert_visibility_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + + BKE_sculpt_update_object_for_edit(depsgraph, ob, false); + + /* Not supported for dyntopo. */ + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + return OPERATOR_CANCELLED; + } + + PBVH *pbvh = ob->sculpt->pbvh; + Vector nodes = blender::bke::pbvh::search_gather(pbvh, {}); + + if (nodes.is_empty()) { + return OPERATOR_CANCELLED; + } + + ss->hide_poly = BKE_sculpt_hide_poly_ensure(ob); + + undo::push_begin(ob, op); + for (PBVHNode *node : nodes) { + undo::push_node(ob, node, undo::Type::HideFace); + } + + visibility_all_invert(ss); + + undo::push_end(ob); + + /* Sync face sets visibility and vertex visibility. */ + hide::sync_all_from_faces(*ob); + + for (PBVHNode *node : nodes) { + BKE_pbvh_node_mark_update_visibility(node); + } + + bke::pbvh::update_visibility(*ss->pbvh); + + SCULPT_tag_update_overlays(C); + + return OPERATOR_FINISHED; +} + +void SCULPT_OT_face_sets_invert_visibility(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Invert Face Set Visibility"; + ot->idname = "SCULPT_OT_face_set_invert_visibility"; + ot->description = "Invert the visibility of the Face Sets of the sculpt"; + + /* Api callbacks. */ + ot->exec = sculpt_face_sets_invert_visibility_exec; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + /* -------------------------------------------------------------------- */ /** \name Gesture Operators * \{ */ @@ -1872,5 +1992,4 @@ void SCULPT_OT_face_set_box_gesture(wmOperatorType *ot) gesture::operator_properties(ot); } /** \} */ - } // namespace blender::ed::sculpt_paint::face_set diff --git a/source/blender/editors/sculpt_paint/sculpt_face_set_topology.c b/source/blender/editors/sculpt_paint/sculpt_face_set_topology.c new file mode 100644 index 00000000000..93773e9e143 --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_face_set_topology.c @@ -0,0 +1,209 @@ +/* + * 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 edsculpt + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_hash.h" +#include "BLI_math.h" +#include "BLI_task.h" + +#include "DNA_brush_types.h" +#include "DNA_customdata_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BKE_brush.h" +#include "BKE_ccg.h" +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_customdata.h" +#include "BKE_mesh.h" +#include "BKE_mesh_fair.h" +#include "BKE_mesh_mapping.h" +#include "BKE_multires.h" +#include "BKE_node.h" +#include "BKE_object.hh" +#include "BKE_paint.h" +#include "BKE_pbvh_api.hh" +#include "BKE_scene.h" + +#include "DEG_depsgraph.h" + +#include "WM_api.h" +#include "WM_message.h" +#include "WM_toolsystem.h" +#include "WM_types.h" + +#include "ED_object.h" +#include "ED_screen.h" +#include "ED_sculpt.h" +#include "ED_view3d.h" +#include "paint_intern.h" +#include "sculpt_intern.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "bmesh.h" + +#include +#include + +typedef enum eSculptFaceSetByTopologyMode { + SCULPT_FACE_SET_TOPOLOGY_LOOSE_PART = 0, + SCULPT_FACE_SET_TOPOLOGY_POLY_LOOP = 1, +} eSculptFaceSetByTopologyMode; + +static EnumPropertyItem prop_sculpt_face_set_by_topology[] = { + { + SCULPT_FACE_SET_TOPOLOGY_LOOSE_PART, + "LOOSE_PART", + 0, + "Loose Part", + "", + }, + { + SCULPT_FACE_SET_TOPOLOGY_POLY_LOOP, + "POLY_LOOP", + 0, + "Face Loop", + "", + }, + {0, NULL, 0, NULL, NULL}, +}; + +static void sculpt_face_set_by_topology_poly_loop(Object *ob, const int next_face_set_id) +{ + SculptSession *ss = ob->sculpt; + BLI_bitmap *poly_loop = sculpt_poly_loop_from_cursor(ob); + for (int i = 0; i < ss->totfaces; i++) { + if (BLI_BITMAP_TEST(poly_loop, i)) { + ss->face_sets[i] = next_face_set_id; + } + } + MEM_freeN(poly_loop); +} + +static int sculpt_face_set_by_topology_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + + const int mode = RNA_enum_get(op->ptr, "mode"); + const bool repeat_previous = RNA_boolean_get(op->ptr, "repeat_previous"); + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false); + printf("FACE SET TOPOLOGY\n"); + + /* Update the current active Face Set and Vertex as the operator can be used directly from the + * tool without brush cursor. */ + SculptCursorGeometryInfo sgi; + const float mouse[2] = {event->mval[0], event->mval[1]}; + if (!SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false)) { + /* The cursor is not over the mesh. Cancel to avoid editing the last updated Face Set ID. */ + return OPERATOR_CANCELLED; + } + + PBVHNode **nodes; + int totnode; + BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode); + SCULPT_undo_push_begin(ob, op); + SCULPT_undo_push_node(ob, nodes[0], undo::Type::FaceSet); + + const PBVHFaceRef initial_poly = ss->active_face; + const PBVHEdgeRef initial_edge = sculpt_poly_loop_initial_edge_from_cursor(ob); + + Mesh *mesh = BKE_object_get_original_mesh(ob); + int new_face_set = SCULPT_FACE_SET_NONE; + + if (repeat_previous && ss->face_set_last_created != SCULPT_FACE_SET_NONE && + initial_poly.i != ss->face_set_last_poly.i && initial_edge.i != ss->face_set_last_edge.i) + { + new_face_set = ss->face_set_last_created; + } + else { + new_face_set = ED_sculpt_face_sets_find_next_available_id(mesh); + } + + switch (mode) { + case SCULPT_FACE_SET_TOPOLOGY_LOOSE_PART: + break; + case SCULPT_FACE_SET_TOPOLOGY_POLY_LOOP: + sculpt_face_set_by_topology_poly_loop(ob, new_face_set); + break; + } + + ss->face_set_last_created = new_face_set; + ss->face_set_last_edge = initial_edge; + ss->face_set_last_poly = initial_poly; + + /* Sync face sets visibility and vertex visibility as now all Face Sets are visible. */ + // SCULPT_visibility_sync_all_face_sets_to_vertices(ob); + + for (int i = 0; i < totnode; i++) { + BKE_pbvh_vert_tag_update_normal_visibility(nodes[i]); + } + + bke::pbvh::update_vertex_data(ss->pbvh, PBVH_UpdateVisibility); + + if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) { + BKE_mesh_flush_hidden_from_verts(ob->data); + } + + MEM_freeN(nodes); + + SCULPT_undo_push_end(ob); + SCULPT_tag_update_overlays(C); + + return OPERATOR_FINISHED; +} + +void SCULPT_OT_face_set_by_topology(struct wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Face Set by Topology"; + ot->idname = "SCULPT_OT_face_set_by_topology"; + ot->description = "Create a new Face Set following the mesh topology"; + + /* Api callbacks. */ + ot->invoke = sculpt_face_set_by_topology_invoke; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_enum(ot->srna, + "mode", + prop_sculpt_face_set_by_topology, + SCULPT_FACE_SET_TOPOLOGY_POLY_LOOP, + "Mode", + ""); + + RNA_def_boolean(ot->srna, + "repeat_previous", + true, + "Repeat previous Face Set", + "Repeat the latest created Face Set instead of a new one"); +} diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_color.cc b/source/blender/editors/sculpt_paint/sculpt_filter_color.cc index 9876e011184..f6af89a3ead 100644 --- a/source/blender/editors/sculpt_paint/sculpt_filter_color.cc +++ b/source/blender/editors/sculpt_paint/sculpt_filter_color.cc @@ -89,7 +89,7 @@ static void color_filter_task(Object *ob, PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); + SCULPT_orig_vert_data_update(&orig_data, vd.vertex); auto_mask::node_update(automask_data, vd); float3 orig_color; @@ -297,7 +297,7 @@ static void sculpt_color_filter_end(bContext *C, Object *ob) SculptSession *ss = ob->sculpt; undo::push_end(ob); - filter::cache_free(ss); + filter::cache_free(ss, ob); SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COLOR); } @@ -340,11 +340,10 @@ static int sculpt_color_filter_init(bContext *C, wmOperator *op) RNA_int_get_array(op->ptr, "start_mouse", mval); float mval_fl[2] = {float(mval[0]), float(mval[1])}; + SCULPT_stroke_id_next(ob); + const bool use_automasking = auto_mask::is_enabled(sd, ss, nullptr); if (use_automasking) { - /* Increment stroke id for auto-masking system. */ - SCULPT_stroke_id_next(ob); - if (v3d) { /* Update the active face set manually as the paint cursor is not enabled when using the Mesh * Filter Tool. */ @@ -361,6 +360,8 @@ static int sculpt_color_filter_init(bContext *C, wmOperator *op) undo::push_begin(ob, op); BKE_sculpt_color_layer_create_if_needed(ob); + BKE_sculpt_ensure_origcolor(ob); + /* CTX_data_ensure_evaluated_depsgraph should be used at the end to include the updates of * earlier steps modifying the data. */ Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_mesh.cc b/source/blender/editors/sculpt_paint/sculpt_filter_mesh.cc index a4d5e7308fc..7bd961aeb5a 100644 --- a/source/blender/editors/sculpt_paint/sculpt_filter_mesh.cc +++ b/source/blender/editors/sculpt_paint/sculpt_filter_mesh.cc @@ -211,7 +211,7 @@ void cache_init(bContext *C, } } -void cache_free(SculptSession *ss) +void cache_free(SculptSession *ss, Object * /*ob*/) { if (ss->filter_cache->cloth_sim) { cloth::simulation_free(ss->filter_cache->cloth_sim); @@ -220,7 +220,6 @@ void cache_free(SculptSession *ss) MEM_SAFE_FREE(ss->filter_cache->prev_mask); MEM_SAFE_FREE(ss->filter_cache->normal_factor); MEM_SAFE_FREE(ss->filter_cache->prev_face_set); - MEM_SAFE_FREE(ss->filter_cache->surface_smooth_laplacian_disp); MEM_SAFE_FREE(ss->filter_cache->sharpen_factor); MEM_SAFE_FREE(ss->filter_cache->detail_directions); MEM_SAFE_FREE(ss->filter_cache->limit_surface_co); @@ -326,6 +325,21 @@ static bool sculpt_mesh_filter_is_continuous(eSculptMeshFilterType type) MESH_FILTER_RELAX_FACE_SETS); } +static void mesh_filter_task_update_boundaries(Object *ob, PBVHNode *node) +{ + SculptSession *ss = ob->sculpt; + + BKE_pbvh_check_tri_areas(ss->pbvh, node); + + /* Ensure boundaries and valences are up to date. */ + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { + SCULPT_vertex_is_boundary(ss, vd.vertex, SCULPT_BOUNDARY_ALL); + SCULPT_vertex_valence_get(ss, vd.vertex); + } + BKE_pbvh_vertex_iter_end; +} + static void mesh_filter_task(Object *ob, const eSculptMeshFilterType filter_type, const float filter_strength, @@ -344,9 +358,12 @@ static void mesh_filter_task(Object *ob, auto_mask::NodeData automask_data = auto_mask::node_begin( *ob, ss->filter_cache->automasking.get(), *node); + /* Smooth parameters. */ + float projection = 0.0f; + PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); + SCULPT_orig_vert_data_update(&orig_data, vd.vertex); auto_mask::node_update(automask_data, vd); float orig_co[3], val[3], avg[3], disp[3], disp2[3], transform[3][3], final_pos[3]; @@ -382,7 +399,8 @@ static void mesh_filter_task(Object *ob, switch (filter_type) { case MESH_FILTER_SMOOTH: fade = clamp_f(fade, -1.0f, 1.0f); - smooth::neighbor_coords_average_interior(ss, avg, vd.vertex); + smooth::neighbor_coords_average_interior( + ss, avg, vd.vertex, projection, ss->filter_cache->hard_corner_pin, true); sub_v3_v3v3(val, avg, orig_co); madd_v3_v3v3fl(val, orig_co, val, fade); sub_v3_v3v3(disp, val, orig_co); @@ -431,23 +449,22 @@ static void mesh_filter_task(Object *ob, break; } case MESH_FILTER_RELAX: { - smooth::relax_vertex(ss, &vd, clamp_f(fade, 0.0f, 1.0f), false, val); + smooth::relax_vertex(ss, &vd, clamp_f(fade, 0.0f, 1.0f), SCULPT_BOUNDARY_MESH, val); sub_v3_v3v3(disp, val, vd.co); break; } case MESH_FILTER_RELAX_FACE_SETS: { - smooth::relax_vertex(ss, &vd, clamp_f(fade, 0.0f, 1.0f), relax_face_sets, val); + eSculptBoundary boundtype = SCULPT_BOUNDARY_MESH; + if (relax_face_sets) { + boundtype |= SCULPT_BOUNDARY_FACE_SET; + } + smooth::relax_vertex(ss, &vd, clamp_f(fade, 0.0f, 1.0f), boundtype, val); sub_v3_v3v3(disp, val, vd.co); break; } case MESH_FILTER_SURFACE_SMOOTH: { - smooth::surface_smooth_laplacian_step(ss, - disp, - vd.co, - ss->filter_cache->surface_smooth_laplacian_disp, - vd.vertex, - orig_data.co, - ss->filter_cache->surface_smooth_shape_preservation); + smooth::surface_smooth_laplacian_step( + ss, disp, vd.co, vd.vertex, orig_data.co, 1.0f, true); break; } case MESH_FILTER_SHARPEN: { @@ -472,7 +489,8 @@ static void mesh_filter_task(Object *ob, float disp_avg[3]; float avg_co[3]; - smooth::neighbor_coords_average(ss, avg_co, vd.vertex); + smooth::neighbor_coords_average( + ss, avg_co, vd.vertex, projection, ss->filter_cache->hard_corner_pin, true); sub_v3_v3v3(disp_avg, avg_co, vd.co); mul_v3_v3fl( disp_avg, disp_avg, smooth_ratio * pow2f(ss->filter_cache->sharpen_factor[vd.index])); @@ -517,6 +535,7 @@ static void mesh_filter_task(Object *ob, add_v3_v3v3(final_pos, orig_co, disp); } copy_v3_v3(vd.co, final_pos); + BKE_sculpt_sharp_boundary_flag_update(ss, vd.vertex); } BKE_pbvh_vertex_iter_end; @@ -534,7 +553,7 @@ static void mesh_filter_enhance_details_init_directions(SculptSession *ss) PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); float avg[3]; - smooth::neighbor_coords_average(ss, avg, vertex); + smooth::neighbor_coords_average(ss, avg, vertex, 0.0f, filter_cache->hard_corner_pin, true); sub_v3_v3v3(filter_cache->detail_directions[i], avg, SCULPT_vertex_co_get(ss, vertex)); } } @@ -546,8 +565,6 @@ static void mesh_filter_surface_smooth_init(SculptSession *ss, const int totvert = SCULPT_vertex_count_get(ss); filter::Cache *filter_cache = ss->filter_cache; - filter_cache->surface_smooth_laplacian_disp = static_cast( - MEM_malloc_arrayN(totvert, sizeof(float[3]), __func__)); filter_cache->surface_smooth_shape_preservation = shape_preservation; filter_cache->surface_smooth_current_vertex = current_vertex_displacement; } @@ -586,7 +603,7 @@ static void mesh_filter_sharpen_init(SculptSession *ss, PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); float avg[3]; - smooth::neighbor_coords_average(ss, avg, vertex); + smooth::neighbor_coords_average(ss, avg, vertex, 0.0f, filter_cache->hard_corner_pin, true); sub_v3_v3v3(filter_cache->detail_directions[i], avg, SCULPT_vertex_co_get(ss, vertex)); filter_cache->sharpen_factor[i] = len_v3(filter_cache->detail_directions[i]); } @@ -656,7 +673,6 @@ static void mesh_filter_surface_smooth_displace_task(Object *ob, smooth::surface_smooth_displace_step(ss, vd.co, - ss->filter_cache->surface_smooth_laplacian_disp, vd.vertex, ss->filter_cache->surface_smooth_current_vertex, clamp_f(fade, 0.0f, 1.0f)); @@ -714,6 +730,27 @@ static void sculpt_mesh_filter_apply(bContext *C, wmOperator *op) SCULPT_vertex_random_access_ensure(ss); + if (filter_type == MESH_FILTER_SURFACE_SMOOTH) { + smooth::surface_smooth_laplacian_init(ob); + } + + ss->filter_cache->preserve_fset_boundaries = !ss->hard_edge_mode; + + if (ELEM(filter_type, + MESH_FILTER_SMOOTH, + MESH_FILTER_SURFACE_SMOOTH, + MESH_FILTER_ENHANCE_DETAILS, + MESH_FILTER_SHARPEN)) + { + BKE_pbvh_face_areas_begin(ob, ss->pbvh); + threading::parallel_for( + ss->filter_cache->nodes.index_range(), 1, [&](const IndexRange &range) { + for (const int i : range) { + mesh_filter_task_update_boundaries(ob, ss->filter_cache->nodes[i]); + } + }); + } + threading::parallel_for(ss->filter_cache->nodes.index_range(), 1, [&](const IndexRange range) { for (const int i : range) { mesh_filter_task( @@ -735,7 +772,8 @@ static void sculpt_mesh_filter_apply(bContext *C, wmOperator *op) SCULPT_flush_stroke_deform(sd, ob, true); } - /* The relax mesh filter needs the updated normals of the modified mesh after each iteration. */ + /* The relax mesh filter needs the updated normals of the modified mesh after each iteration. + */ if (ELEM(MESH_FILTER_RELAX, MESH_FILTER_RELAX_FACE_SETS)) { bke::pbvh::update_normals(*ss->pbvh, ss->subdiv_ccg); } @@ -790,7 +828,7 @@ static void sculpt_mesh_filter_end(bContext *C) Object *ob = CTX_data_active_object(C); SculptSession *ss = ob->sculpt; - cache_free(ss); + cache_free(ss, ob); SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COORDS); } @@ -827,7 +865,7 @@ static void sculpt_mesh_filter_cancel(bContext *C, wmOperator * /*op*/) SCULPT_orig_vert_data_init(&orig_data, ob, node, undo::Type::Position); BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); + SCULPT_orig_vert_data_update(&orig_data, vd.vertex); copy_v3_v3(vd.co, orig_data.co); } @@ -949,7 +987,8 @@ static int sculpt_mesh_filter_start(bContext *C, wmOperator *op) { Object *ob = CTX_data_active_object(C); Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + ToolSettings *tool_settings = CTX_data_tool_settings(C); + Sculpt *sd = tool_settings->sculpt; const View3D *v3d = CTX_wm_view3d(C); const Base *base = CTX_data_active_base(C); @@ -980,11 +1019,10 @@ static int sculpt_mesh_filter_start(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + SCULPT_stroke_id_next(ob); float mval_fl[2] = {float(mval[0]), float(mval[1])}; - if (use_automasking) { - /* Increment stroke id for automasking system. */ - SCULPT_stroke_id_next(ob); + if (use_automasking) { /* Update the active face set manually as the paint cursor is not enabled when using the Mesh * Filter Tool. */ SculptCursorGeometryInfo sgi; @@ -1020,6 +1058,9 @@ static int sculpt_mesh_filter_start(bContext *C, wmOperator *op) RNA_enum_get(op->ptr, "orientation")); ss->filter_cache->orientation = orientation; + ss->filter_cache->hard_corner_pin = RNA_float_get(op->ptr, "hard_corner_pin"); + ss->hard_edge_mode = tool_settings->unified_paint_settings.hard_edge_mode; + return OPERATOR_PASS_THROUGH; } @@ -1096,15 +1137,35 @@ void register_operator_props(wmOperatorType *ot) PropertyRNA *prop = RNA_def_collection_runtime( ot->srna, "event_history", &RNA_OperatorStrokeElement, "", ""); RNA_def_property_flag(prop, PropertyFlag(int(PROP_HIDDEN) | int(PROP_SKIP_SAVE))); + + RNA_def_float(ot->srna, + "hard_corner_pin", + 1.0f, + 0.0, + 1.0f, + "Corner Pin", + "How much to pin corners in hard edge mode", + 0.0f, + 1.0f); } static void sculpt_mesh_ui_exec(bContext * /*C*/, wmOperator *op) { uiLayout *layout = op->layout; - uiItemR(layout, op->ptr, "strength", UI_ITEM_NONE, nullptr, ICON_NONE); - uiItemR(layout, op->ptr, "iteration_count", UI_ITEM_NONE, nullptr, ICON_NONE); - uiItemR(layout, op->ptr, "orientation", UI_ITEM_NONE, nullptr, ICON_NONE); + uiItemR(layout, op->ptr, "strength", eUI_Item_Flag(0), nullptr, ICON_NONE); + if (ELEM(RNA_enum_get(op->ptr, "type"), + MESH_FILTER_SMOOTH, + MESH_FILTER_SURFACE_SMOOTH, + MESH_FILTER_ENHANCE_DETAILS, + MESH_FILTER_SHARPEN)) + { + uiItemR(layout, op->ptr, "hard_corner_pin", eUI_Item_Flag(0), nullptr, ICON_NONE); + } + + uiItemR(layout, op->ptr, "iteration_count", eUI_Item_Flag(0), nullptr, ICON_NONE); + uiItemR(layout, op->ptr, "orientation", eUI_Item_Flag(0), nullptr, ICON_NONE); + layout = uiLayoutRow(layout, true); uiItemR(layout, op->ptr, "deform_axis", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); } diff --git a/source/blender/editors/sculpt_paint/sculpt_geodesic.cc b/source/blender/editors/sculpt_paint/sculpt_geodesic.cc index d932aec92f6..2e448c32d01 100644 --- a/source/blender/editors/sculpt_paint/sculpt_geodesic.cc +++ b/source/blender/editors/sculpt_paint/sculpt_geodesic.cc @@ -11,10 +11,21 @@ #include "MEM_guardedalloc.h" +#include "BLI_alloca.h" +#include "BLI_blenlib.h" +#include "BLI_index_range.hh" #include "BLI_linklist_stack.h" +#include "BLI_map.hh" #include "BLI_math_geom.h" #include "BLI_math_vector.h" +#include "BLI_math_vector_types.hh" +#include "BLI_memarena.h" +#include "BLI_mempool.h" +#include "BLI_set.hh" +#include "BLI_sort_utils.h" #include "BLI_task.h" +#include "BLI_utildefines.h" +#include "BLI_vector.hh" #include "DNA_brush_types.h" #include "DNA_mesh_types.h" @@ -43,7 +54,8 @@ static bool sculpt_geodesic_mesh_test_dist_add(Span vert_positions, const int v1, const int v2, float *dists, - GSet *initial_verts) + GSet *initial_verts, + PBVHVertRef *r_closest_verts) { if (BLI_gset_haskey(initial_verts, POINTER_FROM_INT(v0))) { return false; @@ -54,30 +66,217 @@ static bool sculpt_geodesic_mesh_test_dist_add(Span vert_positions, return false; } + const float *co0 = vert_positions[v0]; + const float *co1 = vert_positions[v1]; + const float *co2 = v2 != SCULPT_GEODESIC_VERTEX_NONE ? vert_positions[v2] : nullptr; + float dist0; if (v2 != SCULPT_GEODESIC_VERTEX_NONE) { BLI_assert(dists[v2] != FLT_MAX); if (dists[v0] <= dists[v2]) { return false; } - dist0 = geodesic_distance_propagate_across_triangle( - vert_positions[v0], vert_positions[v1], vert_positions[v2], dists[v1], dists[v2]); + dist0 = geodesic_distance_propagate_across_triangle(co0, co1, co2, dists[v1], dists[v2]); } else { float vec[3]; - sub_v3_v3v3(vec, vert_positions[v1], vert_positions[v0]); + sub_v3_v3v3(vec, co1, co0); dist0 = dists[v1] + len_v3(vec); } if (dist0 < dists[v0]) { dists[v0] = dist0; + + if (r_closest_verts) { + bool tag1 = r_closest_verts[v1].i != -1LL; + bool tag2 = v2 != SCULPT_GEODESIC_VERTEX_NONE && r_closest_verts[v2].i != -1LL; + + float l1 = len_v3v3(co0, co1); + float l2 = v2 != SCULPT_GEODESIC_VERTEX_NONE ? len_v3v3(co0, co2) : 0.0f; + + if (tag1 && tag2) { + if (l1 < l2) { + r_closest_verts[v0] = r_closest_verts[v1]; + } + else { + r_closest_verts[v0] = r_closest_verts[v2]; + } + } + else if (tag2) { + r_closest_verts[v0] = r_closest_verts[v2]; + } + else if (tag1) { + r_closest_verts[v0] = r_closest_verts[v1]; + } + } return true; } return false; } -static float *geodesic_mesh_create(Object *ob, GSet *initial_verts, const float limit_radius) +/* Propagate distance from v1 and v2 to v0. */ +static bool sculpt_geodesic_grids_test_dist_add(SculptSession *ss, + const int v0, + const int v1, + const int v2, + float *dists, + GSet *initial_verts, + PBVHVertRef *r_closest_verts, + const Span vert_positions) +{ + if (BLI_gset_haskey(initial_verts, POINTER_FROM_INT(v0))) { + return false; + } + + BLI_assert(dists[v1] != FLT_MAX); + if (dists[v0] <= dists[v1]) { + return false; + } + + const float *co0 = !vert_positions.is_empty() ? + &vert_positions[v0][0] : + SCULPT_vertex_co_get(ss, BKE_pbvh_index_to_vertex(ss->pbvh, v0)); + const float *co1 = !vert_positions.is_empty() ? + &vert_positions[v1][0] : + SCULPT_vertex_co_get(ss, BKE_pbvh_index_to_vertex(ss->pbvh, v1)); + const float *co2 = v2 != SCULPT_GEODESIC_VERTEX_NONE ? + (!vert_positions.is_empty() ? + &vert_positions[v2][0] : + SCULPT_vertex_co_get(ss, BKE_pbvh_index_to_vertex(ss->pbvh, v2))) : + nullptr; + + float dist0; + if (v2 != SCULPT_GEODESIC_VERTEX_NONE) { + BLI_assert(dists[v2] != FLT_MAX); + if (dists[v0] <= dists[v2]) { + return false; + } + dist0 = geodesic_distance_propagate_across_triangle(co0, co1, co2, dists[v1], dists[v2]); + } + else { + float vec[3]; + sub_v3_v3v3(vec, co1, co0); + dist0 = dists[v1] + len_v3(vec); + } + + if (dist0 < dists[v0]) { + dists[v0] = dist0; + + if (r_closest_verts) { + bool tag1 = r_closest_verts[v1].i != -1LL; + bool tag2 = v2 != SCULPT_GEODESIC_VERTEX_NONE && r_closest_verts[v2].i != -1LL; + + float l1 = len_v3v3(co0, co1); + float l2 = v2 != SCULPT_GEODESIC_VERTEX_NONE ? len_v3v3(co0, co2) : 0.0f; + + if (tag1 && tag2) { + if (l1 < l2) { + r_closest_verts[v0] = r_closest_verts[v1]; + } + else { + r_closest_verts[v0] = r_closest_verts[v2]; + } + } + else if (tag2) { + r_closest_verts[v0] = r_closest_verts[v2]; + } + else if (tag1) { + r_closest_verts[v0] = r_closest_verts[v1]; + } + } + return true; + } + + return false; +} + +#define BMESH_INITIAL_VERT_TAG BM_ELEM_TAG_ALT + +static bool sculpt_geodesic_mesh_test_dist_add_bmesh(BMVert *v0, + BMVert *v1, + BMVert *v2, + float *dists, + GSet * /*initial_verts*/, + PBVHVertRef *r_closest_verts, + const Span vert_positions) +{ + const int v0_i = BM_elem_index_get(v0); + const int v1_i = BM_elem_index_get(v1); + const int v2_i = v2 ? BM_elem_index_get(v2) : SCULPT_GEODESIC_VERTEX_NONE; + + const float *v0co = !vert_positions.is_empty() ? vert_positions[BM_elem_index_get(v0)] : v0->co; + const float *v1co = !vert_positions.is_empty() ? vert_positions[BM_elem_index_get(v1)] : v1->co; + const float *v2co = v2 ? (!vert_positions.is_empty() ? vert_positions[BM_elem_index_get(v2)] : + v2->co) : + nullptr; + + if (BM_elem_flag_test(v0, BMESH_INITIAL_VERT_TAG)) { + return false; + } + + BLI_assert(dists[v1_i] != FLT_MAX); + if (dists[v0_i] <= dists[v1_i]) { + return false; + } + + float dist0; + if (v2_i != SCULPT_GEODESIC_VERTEX_NONE) { + BLI_assert(dists[v2_i] != FLT_MAX); + if (dists[v0_i] <= dists[v2_i]) { + return false; + } + + dist0 = geodesic_distance_propagate_across_triangle( + v0co, v1co, v2co, dists[v1_i], dists[v2_i]); + } + else { + float vec[3]; + sub_v3_v3v3(vec, v1co, v0co); + dist0 = dists[v1_i] + len_v3(vec); + } + + if (dist0 < dists[v0_i]) { + dists[v0_i] = dist0; + + if (r_closest_verts) { + bool tag1 = r_closest_verts[v1_i].i != -1LL; + bool tag2 = v2 && r_closest_verts[v2_i].i != -1LL; + + float l1 = len_v3v3(v0co, v1co); + float l2 = v2 ? len_v3v3(v0co, v2co) : 0.0f; + + if (!tag1 && !tag2) { + printf("bad\n"); + } + + if (tag1 && tag2) { + if (l1 < l2) { // dists[v1_i] < dists[v2_i]) { + r_closest_verts[v0_i] = r_closest_verts[v1_i]; + } + else { + r_closest_verts[v0_i] = r_closest_verts[v2_i]; + } + } + else if (tag2) { + r_closest_verts[v0_i] = r_closest_verts[v2_i]; + } + else if (tag1) { + r_closest_verts[v0_i] = r_closest_verts[v1_i]; + } + } + + return true; + } + + return false; +} + +static float *geodesic_mesh_create(Object *ob, + GSet *initial_verts, + const float limit_radius, + PBVHVertRef *r_closest_verts, + Span vert_positions) { SculptSession *ss = ob->sculpt; Mesh *mesh = BKE_object_get_original_mesh(ob); @@ -87,7 +286,9 @@ static float *geodesic_mesh_create(Object *ob, GSet *initial_verts, const float const float limit_radius_sq = limit_radius * limit_radius; - const Span vert_positions = SCULPT_mesh_deformed_positions_get(ss); + if (vert_positions.is_empty()) { + vert_positions = SCULPT_mesh_deformed_positions_get(ss); + } const Span edges = mesh->edges(); const OffsetIndices faces = mesh->faces(); const Span corner_verts = mesh->corner_verts(); @@ -107,6 +308,12 @@ static float *geodesic_mesh_create(Object *ob, GSet *initial_verts, const float edges, mesh->verts_num, ss->vert_to_edge_offsets, ss->vert_to_edge_indices); } + if (r_closest_verts) { + for (int i = 0; i < totvert; i++) { + r_closest_verts[i].i = -1LL; + } + } + /* Both contain edge indices encoded as *void. */ BLI_LINKSTACK_DECLARE(queue, void *); BLI_LINKSTACK_DECLARE(queue_next, void *); @@ -117,6 +324,10 @@ static float *geodesic_mesh_create(Object *ob, GSet *initial_verts, const float for (int i = 0; i < totvert; i++) { if (BLI_gset_haskey(initial_verts, POINTER_FROM_INT(i))) { dists[i] = 0.0f; + + if (r_closest_verts) { + r_closest_verts[i] = BKE_pbvh_index_to_vertex(ss->pbvh, i); + } } else { dists[i] = FLT_MAX; @@ -170,8 +381,13 @@ static float *geodesic_mesh_create(Object *ob, GSet *initial_verts, const float if (dists[v1] > dists[v2]) { std::swap(v1, v2); } - sculpt_geodesic_mesh_test_dist_add( - vert_positions, v2, v1, SCULPT_GEODESIC_VERTEX_NONE, dists, initial_verts); + sculpt_geodesic_mesh_test_dist_add(vert_positions, + v2, + v1, + SCULPT_GEODESIC_VERTEX_NONE, + dists, + initial_verts, + r_closest_verts); } for (const int face : ss->edge_to_face_map[e]) { @@ -183,7 +399,7 @@ static float *geodesic_mesh_create(Object *ob, GSet *initial_verts, const float continue; } if (sculpt_geodesic_mesh_test_dist_add( - vert_positions, v_other, v1, v2, dists, initial_verts)) + vert_positions, v_other, v1, v2, dists, initial_verts, r_closest_verts)) { for (const int e_other : ss->vert_to_edge_map[v_other]) { int ev_other; @@ -223,8 +439,496 @@ static float *geodesic_mesh_create(Object *ob, GSet *initial_verts, const float return dists; } +static float *geodesic_bmesh_create(Object *ob, + GSet *initial_verts, + const float limit_radius, + PBVHVertRef *r_closest_verts, + const Span vert_positions) +{ + SculptSession *ss = ob->sculpt; + + bool pos_override = !vert_positions.is_empty(); + + if (!ss->bm) { + return nullptr; + } + + BM_mesh_elem_index_ensure(ss->bm, BM_VERT | BM_EDGE | BM_FACE); + + const int totvert = ss->bm->totvert; + const int totedge = ss->bm->totedge; + + if (r_closest_verts) { + for (int i = 0; i < totvert; i++) { + r_closest_verts[i].i = -1LL; + } + } + + const float limit_radius_sq = limit_radius * limit_radius; + + float *dists = static_cast(MEM_malloc_arrayN(totvert, sizeof(float), "distances")); + BitVector<> edge_tag(totedge); + + BLI_LINKSTACK_DECLARE(queue, BMEdge *); + BLI_LINKSTACK_DECLARE(queue_next, BMEdge *); + + BLI_LINKSTACK_INIT(queue); + BLI_LINKSTACK_INIT(queue_next); + + for (int i = 0; i < totvert; i++) { + if (BLI_gset_haskey(initial_verts, POINTER_FROM_INT(i))) { + dists[i] = 0.0f; + + if (r_closest_verts) { + r_closest_verts[i] = BKE_pbvh_index_to_vertex(ss->pbvh, i); + } + } + else { + dists[i] = FLT_MAX; + } + } + + /* Masks verts that are further than limit radius from an initial vertex. As there is no + * need to define a distance to them the algorithm can stop earlier by skipping them. */ + BitVector<> affected_vertex(totvert); + GSetIterator gs_iter; + + BMVert *v; + BMIter iter; + + BM_ITER_MESH (v, &iter, ss->bm, BM_VERTS_OF_MESH) { + BM_elem_flag_disable(v, BMESH_INITIAL_VERT_TAG); + } + + if (limit_radius == FLT_MAX) { + /* In this case, no need to loop through all initial verts to check distances as they are + * all going to be affected. */ + affected_vertex.fill(true); + } + else { + /* This is an O(n^2) loop used to limit the geodesic distance calculation to a radius. + * When this optimization is needed, it is expected for the tool to request the distance + * to a low number of verts (usually just 1 or 2). */ + GSET_ITER (gs_iter, initial_verts) { + const int v_i = POINTER_AS_INT(BLI_gsetIterator_getKey(&gs_iter)); + BMVert *v = (BMVert *)BKE_pbvh_index_to_vertex(ss->pbvh, v_i).i; + const float *co1 = pos_override ? vert_positions[BM_elem_index_get(v)] : v->co; + + BM_elem_flag_enable(v, BMESH_INITIAL_VERT_TAG); + + for (int i = 0; i < totvert; i++) { + BMVert *v2 = (BMVert *)BKE_pbvh_index_to_vertex(ss->pbvh, i).i; + const float *co2 = pos_override ? vert_positions[BM_elem_index_get(v2)] : v2->co; + + if (len_squared_v3v3(co1, co2) <= limit_radius_sq) { + affected_vertex[i].set(); + } + } + } + } + + BMEdge *e; + + BM_ITER_MESH (e, &iter, ss->bm, BM_EDGES_OF_MESH) { + const int v1_i = BM_elem_index_get(e->v1); + const int v2_i = BM_elem_index_get(e->v2); + + if (!affected_vertex[v1_i].test() && !affected_vertex[v2_i].test()) { + continue; + } + if (dists[v1_i] != FLT_MAX || dists[v2_i] != FLT_MAX) { + BLI_LINKSTACK_PUSH(queue, e); + } + } + + do { + while (BLI_LINKSTACK_SIZE(queue)) { + BMEdge *e = (BMEdge *)BLI_LINKSTACK_POP(queue); + + BMVert *v1 = e->v1, *v2 = e->v2; + int v1_i = BM_elem_index_get(e->v1); + int v2_i = BM_elem_index_get(e->v2); + + if ((dists[v1_i] == FLT_MAX || dists[v2_i] == FLT_MAX) && v1 && v2) { + if (dists[v1_i] > dists[v2_i]) { + SWAP(BMVert *, v1, v2); + SWAP(int, v1_i, v2_i); + } + sculpt_geodesic_mesh_test_dist_add_bmesh( + v2, v1, nullptr, dists, initial_verts, r_closest_verts, vert_positions); + } + + BMLoop *l = e->l; + if (l) { + do { + BMFace *f = l->f; + BMLoop *l2 = f->l_first; + + if (BM_ELEM_CD_GET_INT(f, ss->cd_faceset_offset) < 0) { + l = l->radial_next; + continue; + } + + do { + BMVert *v_other = l2->v; + + if (ELEM(v_other, v1, v2)) { + l2 = l2->next; + continue; + } + + const int v_other_i = BM_elem_index_get(v_other); + + if (sculpt_geodesic_mesh_test_dist_add_bmesh( + v_other, v1, v2, dists, initial_verts, r_closest_verts, vert_positions)) + { + BMIter eiter; + BMEdge *e_other; + + BM_ITER_ELEM (e_other, &eiter, v_other, BM_EDGES_OF_VERT) { + BMVert *ev_other; + + if (e_other->v1 == v_other) { + ev_other = e_other->v2; + } + else { + ev_other = e_other->v1; + } + + const int ev_other_i = BM_elem_index_get(ev_other); + const int e_other_i = BM_elem_index_get(e_other); + + bool ok = e_other != e && !edge_tag[e_other_i].test(); + ok = ok && (!e_other->l || dists[ev_other_i] != FLT_MAX); + ok = ok && + (affected_vertex[v_other_i].test() || affected_vertex[ev_other_i].test()); + + if (ok) { + edge_tag[e_other_i].set(); + BLI_LINKSTACK_PUSH(queue_next, e_other); + } + } + } + + l2 = l2->next; + } while (l2 != f->l_first); + + l = l->radial_next; + } while (l != e->l); + } + } + + for (LinkNode *lnk = queue_next; lnk; lnk = lnk->next) { + BMEdge *e = (BMEdge *)lnk->link; + const int e_i = BM_elem_index_get(e); + + edge_tag[e_i].reset(); + } + + BLI_LINKSTACK_SWAP(queue, queue_next); + + } while (BLI_LINKSTACK_SIZE(queue)); + + BLI_LINKSTACK_FREE(queue); + BLI_LINKSTACK_FREE(queue_next); + + return dists; +} + +BLI_INLINE void *hash_edge(int v1, int v2, int totvert) +{ + if (v1 > v2) { + SWAP(int, v1, v2); + } + + intptr_t ret = (intptr_t)v1 + (intptr_t)v2 * (intptr_t)totvert; + return (void *)ret; +} + +typedef struct TempEdge { + int v1, v2; +} TempEdge; + +int find_quad(TempEdge *edges, MeshElemMap *vmap, int v1, int v2, int v3) +{ + for (int i = 0; i < vmap[v1].count; i++) { + TempEdge *te = edges + vmap[v1].indices[i]; + int v = v1 == te->v1 ? te->v2 : te->v1; + + if (v == v2) { + continue; + } + + for (int j = 0; j < vmap[v].count; j++) { + TempEdge *te2 = edges + vmap[v].indices[j]; + int v4 = v == te2->v1 ? te2->v2 : te2->v1; + + if (v4 == v3) { + return v; + } + } + } + + return -1; +} + +static float *geodesic_grids_create(Object *ob, + GSet *initial_verts, + const float limit_radius, + PBVHVertRef *r_closest_verts, + const Span vert_positions) +{ + SculptSession *ss = ob->sculpt; + + const bool pos_override = !vert_positions.is_empty(); + + const int totvert = SCULPT_vertex_count_get(ss); + + const float limit_radius_sq = limit_radius * limit_radius; + + float *dists = static_cast(MEM_malloc_arrayN(totvert, sizeof(float), "distances")); + + /* Both contain edge indices encoded as *void. */ + BLI_LINKSTACK_DECLARE(queue, void *); + BLI_LINKSTACK_DECLARE(queue_next, void *); + + BLI_LINKSTACK_INIT(queue); + BLI_LINKSTACK_INIT(queue_next); + + for (int i = 0; i < totvert; i++) { + if (BLI_gset_haskey(initial_verts, POINTER_FROM_INT(i))) { + if (r_closest_verts) { + r_closest_verts[i] = BKE_pbvh_index_to_vertex(ss->pbvh, i); + } + + dists[i] = 0.0f; + } + else { + if (r_closest_verts) { + r_closest_verts[i].i = -1LL; + } + + dists[i] = FLT_MAX; + } + } + + /* Masks verts that are further than limit radius from an initial vertex. As there is no + * need to define a distance to them the algorithm can stop earlier by skipping them. */ + BitVector<> affected_vertex(totvert); + GSetIterator gs_iter; + + if (limit_radius == FLT_MAX) { + /* In this case, no need to loop through all initial verts to check distances as they are + * all going to be affected. */ + affected_vertex.fill(true); + } + else { + /* This is an O(n^2) loop used to limit the geodesic distance calculation to a radius. + * When this optimization is needed, it is expected for the tool to request the distance + * to a low number of verts (usually just 1 or 2). */ + GSET_ITER (gs_iter, initial_verts) { + const int v = POINTER_AS_INT(BLI_gsetIterator_getKey(&gs_iter)); + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, v); + const float *v_co = pos_override ? &vert_positions[v][0] : SCULPT_vertex_co_get(ss, vertex); + + for (int i = 0; i < totvert; i++) { + const float *v_co2 = pos_override ? + &vert_positions[i][0] : + SCULPT_vertex_co_get(ss, BKE_pbvh_index_to_vertex(ss->pbvh, i)); + if (len_squared_v3v3(v_co, v_co2) <= limit_radius_sq) { + affected_vertex[i].set(); + } + } + } + } + + SculptVertexNeighborIter ni; + + Vector edges; + + GHash *ehash = BLI_ghash_ptr_new("geodesic multigrids ghash"); + + MeshElemMap *vmap = static_cast( + MEM_calloc_arrayN(totvert, sizeof(*vmap), "geodesic grids vmap")); + + int totedge = 0; + MemArena *ma = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, "geodesic grids memarena"); + + for (int i = 0; i < totvert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + MeshElemMap *map = vmap + i; + + int val = SCULPT_vertex_valence_get(ss, vertex); + map->count = val; + map->indices = (int *)BLI_memarena_alloc(ma, sizeof(int) * val); + + int j = 0; + + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { + void *ekey = hash_edge(i, ni.index, totvert); + void **val; + + if (!BLI_ghash_ensure_p(ehash, ekey, &val)) { + *val = POINTER_FROM_INT(totedge); + + TempEdge te = {i, ni.index}; + edges.append(te); + totedge++; + } + + map->indices[j] = POINTER_AS_INT(*val); + j++; + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + } + + int(*e_otherv_map)[4] = static_cast( + MEM_malloc_arrayN(totedge, sizeof(*e_otherv_map), "e_otherv_map")); + + // create an edge map of opposite edge verts in (up to 2) adjacent faces + for (int i = 0; i < totedge; i++) { + int v1a = -1, v2a = -1; + int v1b = -1, v2b = -1; + + TempEdge *te = &edges[i]; + + for (int j = 0; j < vmap[te->v1].count; j++) { + TempEdge *te2 = &edges[vmap[te->v1].indices[j]]; + int v3 = te->v1 == te2->v1 ? te2->v2 : te2->v1; + + if (v3 == te->v2) { + continue; + } + + int p = find_quad(edges.data(), vmap, te->v1, te->v2, v3); + + if (p != -1) { + v1a = p; + v1b = v3; + } + } + + for (int j = 0; j < vmap[te->v2].count; j++) { + TempEdge *te2 = &edges[vmap[te->v2].indices[j]]; + int v3 = te->v2 == te2->v1 ? te2->v2 : te2->v1; + + if (v3 == te->v1) { + continue; + } + + int p = find_quad(edges.data(), vmap, te->v1, te->v2, v3); + + if (p != -1) { + if (v1a != -1) { + v2a = p; + v2b = v3; + } + else { + v1a = p; + v1b = v3; + } + } + } + + e_otherv_map[i][0] = v1a; + e_otherv_map[i][1] = v1b; + e_otherv_map[i][2] = v2a; + e_otherv_map[i][3] = v2b; + } + + BitVector<> edge_tag(totedge); + + /* Add edges adjacent to an initial vertex to the queue. */ + for (int i = 0; i < totedge; i++) { + const int v1 = edges[i].v1; + const int v2 = edges[i].v2; + + if (!affected_vertex[v1].test() && !affected_vertex[v2].test()) { + continue; + } + if (dists[v1] != FLT_MAX || dists[v2] != FLT_MAX) { + BLI_LINKSTACK_PUSH(queue, POINTER_FROM_INT(i)); + } + } + + do { + while (BLI_LINKSTACK_SIZE(queue)) { + const int e = POINTER_AS_INT(BLI_LINKSTACK_POP(queue)); + int v1 = edges[e].v1; + int v2 = edges[e].v2; + + if (dists[v1] == FLT_MAX || dists[v2] == FLT_MAX) { + if (dists[v1] > dists[v2]) { + SWAP(int, v1, v2); + } + sculpt_geodesic_grids_test_dist_add(ss, + v2, + v1, + SCULPT_GEODESIC_VERTEX_NONE, + dists, + initial_verts, + r_closest_verts, + vert_positions); + } + + for (int pi = 0; pi < 4; pi++) { + int v_other = e_otherv_map[e][pi]; + + if (v_other == -1) { + continue; + } + + // XXX not sure how to handle face sets here - joeedh + // if (ss->face_sets[poly] <= 0) { + // continue; + //} + + if (sculpt_geodesic_grids_test_dist_add( + ss, v_other, v1, v2, dists, initial_verts, r_closest_verts, vert_positions)) + { + for (int edge_map_index = 0; edge_map_index < vmap[v_other].count; edge_map_index++) { + const int e_other = vmap[v_other].indices[edge_map_index]; + int ev_other; + if (edges[e_other].v1 == (uint)v_other) { + ev_other = edges[e_other].v2; + } + else { + ev_other = edges[e_other].v1; + } + + if (e_other != e && !edge_tag[e_other].test() && (dists[ev_other] != FLT_MAX)) { + if (affected_vertex[v_other].test() || affected_vertex[ev_other].test()) { + edge_tag[e_other].set(); + BLI_LINKSTACK_PUSH(queue_next, POINTER_FROM_INT(e_other)); + } + } + } + } + } + } + + for (LinkNode *lnk = queue_next; lnk; lnk = lnk->next) { + const int e = POINTER_AS_INT(lnk->link); + edge_tag[e].reset(); + } + + BLI_LINKSTACK_SWAP(queue, queue_next); + + } while (BLI_LINKSTACK_SIZE(queue)); + + BLI_LINKSTACK_FREE(queue); + BLI_LINKSTACK_FREE(queue_next); + + BLI_memarena_free(ma); + BLI_ghash_free(ehash, nullptr, nullptr); + MEM_SAFE_FREE(vmap); + MEM_SAFE_FREE(e_otherv_map); + + return dists; +} + /* For sculpt mesh data that does not support a geodesic distances algorithm, fallback to the - * distance to each vertex. In this case, only one of the initial vertices will be used to + * distance to each vertex. In this case, only one of the initial verts will be used to * calculate the distance. */ static float *geodesic_fallback_create(Object *ob, GSet *initial_verts) { @@ -233,6 +937,7 @@ static float *geodesic_fallback_create(Object *ob, GSet *initial_verts) const int totvert = mesh->verts_num; float *dists = static_cast(MEM_malloc_arrayN(totvert, sizeof(float), __func__)); int first_affected = SCULPT_GEODESIC_VERTEX_NONE; + GSetIterator gs_iter; GSET_ITER (gs_iter, initial_verts) { first_affected = POINTER_AS_INT(BLI_gsetIterator_getKey(&gs_iter)); @@ -249,23 +954,31 @@ static float *geodesic_fallback_create(Object *ob, GSet *initial_verts) const float *first_affected_co = SCULPT_vertex_co_get( ss, BKE_pbvh_index_to_vertex(ss->pbvh, first_affected)); for (int i = 0; i < totvert; i++) { - PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); - - dists[i] = len_v3v3(first_affected_co, SCULPT_vertex_co_get(ss, vertex)); + dists[i] = len_v3v3(first_affected_co, + SCULPT_vertex_co_get(ss, BKE_pbvh_index_to_vertex(ss->pbvh, i))); } return dists; } -float *distances_create(Object *ob, GSet *initial_verts, const float limit_radius) +float *distances_create(Object *ob, + GSet *initial_verts, + const float limit_radius, + PBVHVertRef *r_closest_verts, + Span vertco_override) { SculptSession *ss = ob->sculpt; switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: - return geodesic_mesh_create(ob, initial_verts, limit_radius); + return geodesic_mesh_create( + ob, initial_verts, limit_radius, r_closest_verts, vertco_override); case PBVH_BMESH: + return geodesic_bmesh_create( + ob, initial_verts, limit_radius, r_closest_verts, vertco_override); case PBVH_GRIDS: - return geodesic_fallback_create(ob, initial_verts); + return geodesic_grids_create( + ob, initial_verts, limit_radius, r_closest_verts, vertco_override); + // return SCULPT_geodesic_fallback_create(ob, initial_verts); } BLI_assert_unreachable(); return nullptr; @@ -297,9 +1010,25 @@ float *distances_create_from_vert_and_symm(Object *ob, } } - float *dists = distances_create(ob, initial_verts, limit_radius); + float *dists = distances_create(ob, initial_verts, limit_radius, nullptr, {}); BLI_gset_free(initial_verts, nullptr); return dists; } +float *SCULPT_geodesic_from_vertex(Object *ob, const PBVHVertRef vertex, const float limit_radius) +{ + SculptSession *ss = ob->sculpt; + + SCULPT_vertex_random_access_ensure(ss); + + GSet *initial_verts = BLI_gset_int_new("initial_verts"); + + BLI_gset_add(initial_verts, POINTER_FROM_INT(BKE_pbvh_vertex_to_index(ss->pbvh, vertex))); + + float *dists = geodesic::distances_create(ob, initial_verts, limit_radius, nullptr, {}); + BLI_gset_free(initial_verts, nullptr); + + return dists; +} + } // namespace blender::ed::sculpt_paint::geodesic diff --git a/source/blender/editors/sculpt_paint/sculpt_gradient.c b/source/blender/editors/sculpt_paint/sculpt_gradient.c new file mode 100644 index 00000000000..c1eff65aae0 --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_gradient.c @@ -0,0 +1,263 @@ +/* + * 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 edsculpt + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BLI_blenlib.h" +#include "BLI_hash.h" +#include "BLI_math.h" +#include "BLI_math_color_blend.h" +#include "BLI_task.h" + +#include "BKE_brush.h" +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_mesh.h" +#include "BKE_mesh_mapping.h" +#include "BKE_object.hh" +#include "BKE_paint.h" +#include "BKE_pbvh_api.hh" +#include "BKE_scene.h" + +#include "IMB_colormanagement.h" + +#include "DEG_depsgraph.h" + +#include "WM_api.h" +#include "WM_message.h" +#include "WM_toolsystem.h" +#include "WM_types.h" + +#include "ED_object.h" +#include "ED_screen.h" +#include "ED_sculpt.h" +#include "paint_intern.h" +#include "sculpt_intern.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "UI_interface.h" + +#include "bmesh.h" + +#include +#include + +static EnumPropertyItem prop_sculpt_gradient_type[] = { + {SCULPT_GRADIENT_LINEAR, "LINEAR", 0, "Linear", ""}, + {SCULPT_GRADIENT_SPHERICAL, "SPHERICAL", 0, "Spherical", ""}, + {SCULPT_GRADIENT_RADIAL, "RADIAL", 0, "Radial", ""}, + {SCULPT_GRADIENT_ANGLE, "ANGLE", 0, "Angle", ""}, + {SCULPT_GRADIENT_REFLECTED, "REFLECTED", 0, "Reflected", ""}, + {0, NULL, 0, NULL, NULL}, +}; + +static void sculpt_gradient_apply_task_cb(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + Sculpt *sd = data->sd; + SculptGradientContext *gcontext = ss->filter_cache->gradient_context; + + SculptOrigVertData orig_data; + AutomaskingNodeData automask_data; + + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n], undo::Type::Position); + SCULPT_automasking_node_begin( + data->ob, ss, ss->filter_cache->automasking, &automask_data, data->nodes[n]); + + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + SCULPT_automasking_node_update(ss, &automask_data, &vd); + + float fade = vd.mask ? *vd.mask : 0.0f; + fade *= SCULPT_automasking_factor_get( + ss->filter_cache->automasking, ss, vd.vertex, &automask_data); + if (fade == 0.0f) { + continue; + } + + float world_co[3]; + float projected_co[3]; + + /* TODO: Implement symmetry by flipping this coordinate. */ + float symm_co[3]; + copy_v3_v3(symm_co, vd.co); + + mul_v3_m4v3(world_co, data->ob->object_to_world, symm_co); + /* TOOD: Implement this again. */ + /* ED_view3d_project(gcontext->vc.region, world_co, projected_co); */ + + float gradient_value = 0.0f; + switch (gcontext->gradient_type) { + case SCULPT_GRADIENT_LINEAR: + + break; + case SCULPT_GRADIENT_SPHERICAL: + + break; + case SCULPT_GRADIENT_RADIAL: { + const float dist = len_v2v2(projected_co, gcontext->line_points[0]); + gradient_value = dist / gcontext->line_length; + } break; + case SCULPT_GRADIENT_ANGLE: + break; + case SCULPT_GRADIENT_REFLECTED: + + break; + } + + gradient_value = clamp_f(gradient_value, 0.0f, 1.0f); + gcontext->sculpt_gradient_apply_for_element(sd, ss, &orig_data, &vd, gradient_value, fade); + if (vd.is_mesh) { + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); + } + } + BKE_pbvh_vertex_iter_end; + gcontext->sculpt_gradient_node_update(data->nodes[n]); +} + +static int sculpt_gradient_update_exec(bContext *C, wmOperator *op, const wmEvent *event) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + SculptGradientContext *gcontext = ss->filter_cache->gradient_context; + + if (event->type != MOUSEMOVE) { + return OPERATOR_RUNNING_MODAL; + } + + gcontext->line_points[0][0] = RNA_int_get(op->ptr, "xstart"); + gcontext->line_points[0][1] = RNA_int_get(op->ptr, "ystart"); + gcontext->line_points[1][0] = RNA_int_get(op->ptr, "xend"); + gcontext->line_points[1][1] = RNA_int_get(op->ptr, "yend"); + gcontext->line_length = len_v2v2(gcontext->line_points[0], gcontext->line_points[1]); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .nodes = ss->filter_cache->nodes, + }; + + TaskParallelSettings settings; + BLI_parallel_range_settings_defaults(&settings); + + BKE_pbvh_parallel_range_settings(&settings, true, ss->filter_cache->totnode); + BLI_task_parallel_range( + 0, ss->filter_cache->totnode, &data, sculpt_gradient_apply_task_cb, &settings); + + SCULPT_flush_update_step(C, ss->filter_cache->gradient_context->update_type); + + return OPERATOR_RUNNING_MODAL; +} + +static void sculpt_gradient_properties(wmOperatorType *ot) +{ + RNA_def_enum( + ot->srna, "type", prop_sculpt_gradient_type, SCULPT_GRADIENT_LINEAR, "Gradient Type", ""); +} + +static void sculpt_gradient_context_init_common(bContext *C, + wmOperator *op, + const wmEvent *event, + SculptGradientContext *gcontext) +{ + /* View Context. */ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ED_view3d_viewcontext_init(C, &gcontext->vc, depsgraph); + + /* Properties */ + gcontext->gradient_type = RNA_enum_get(op->ptr, "type"); + gcontext->strength = RNA_float_get(op->ptr, "strength"); + + /* Symmetry. */ + Object *ob = gcontext->vc.obact; + gcontext->symm = SCULPT_mesh_symmetry_xyz_get(ob); + + /* Depth */ + SculptCursorGeometryInfo sgi; + float mouse[2] = {event->mval[0], event->mval[1]}; + const bool hit = SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false); + if (hit) { + copy_v3_v3(gcontext->depth_point, sgi.location); + } + else { + zero_v3(gcontext->depth_point); + } +} + +static SculptGradientContext *sculpt_mask_gradient_context_create(Object *ob, wmOperator *op) +{ + SculptGradientContext *gradient_context = MEM_callocN(sizeof(SculptGradientContext), + "gradient context"); + gradient_context->update_type = SCULPT_UPDATE_MASK; + return gradient_context; +} + +static int sculpt_mask_gradient_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + + SCULPT_vertex_random_access_ensure(ss); + BKE_sculpt_update_object_for_edit(depsgraph, ob, false, true, false); + + // XXX get area_normal_radius argument properly + SCULPT_filter_cache_init(C, ob, sd, undo::Type::Mask, event->mval, 0.25f, 1.0f); + + ss->filter_cache->gradient_context = sculpt_mask_gradient_context_create(ob, op); + sculpt_gradient_context_init_common(C, op, event, ss->filter_cache->gradient_context); + + return WM_gesture_straightline_invoke(C, op, event); +} + +void SCULPT_OT_mask_gradient(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Mask Gradient"; + ot->idname = "SCULPT_OT_mask_gradient"; + ot->description = "Creates or modifies the mask using a gradient"; + + /* api callbacks */ + /* + ot->invoke = WM_gesture_straightline_invoke; + ot->modal = WM_gesture_straightline_modal; + ot->exec = sculpt_gradient_update_exec; + + ot->poll = SCULPT_mode_poll; + */ + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* rna */ + sculpt_gradient_properties(ot); +} diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.hh b/source/blender/editors/sculpt_paint/sculpt_intern.hh index fc20a68bdb6..b38a4e27991 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.hh +++ b/source/blender/editors/sculpt_paint/sculpt_intern.hh @@ -8,11 +8,20 @@ #pragma once -#include +#include "DNA_brush_enums.h" /* For eAttrCorrectMode. */ +#include "DNA_brush_types.h" +#include "DNA_key_types.h" +#include "DNA_listBase.h" +#include "DNA_scene_types.h" +#include "DNA_vec_types.h" +#include "BKE_attribute.h" #include "BKE_attribute.hh" +#include "BKE_dyntopo.hh" #include "BKE_paint.hh" #include "BKE_pbvh_api.hh" +#include "BKE_sculpt.h" +#include "BKE_sculpt.hh" #include "BLI_array.hh" #include "BLI_bit_vector.hh" @@ -21,24 +30,49 @@ #include "BLI_math_vector_types.hh" #include "BLI_set.hh" #include "BLI_span.hh" +#include "BLI_threads.h" +#include "BLI_utildefines.h" #include "BLI_vector.hh" -#include "DNA_brush_enums.h" - #include "ED_view3d.hh" +#include "bmesh.hh" + +#include +#include +#include + namespace blender::ed::sculpt_paint { + +namespace undo { +enum class Type { + None = 0, + Position = 1 << 0, + HideVert = 1 << 1, + HideFace = 1 << 2, + Mask = 1 << 3, + DyntopoBegin = 1 << 4, + DyntopoEnd = 1 << 5, + DyntopoSymmetrize = 1 << 6, + Geometry = 1 << 7, + FaceSet = 1 << 8, + Color = 1 << 9, +}; +ENUM_OPERATORS(Type, Type::Color); +} // namespace undo + namespace auto_mask { struct NodeData; struct Cache; -} +} // namespace auto_mask namespace cloth { struct SimulationData; } namespace undo { struct Node; } -} +} // namespace blender::ed::sculpt_paint + struct BMLog; struct Dial; struct DistRayAABB_Precalc; @@ -48,6 +82,10 @@ struct KeyBlock; struct Object; struct SculptProjectVector; struct bContext; +struct BrushChannelSet; +struct TaskParallelTLS; + +enum ePaintSymmetryFlags; struct PaintModeSettings; struct WeightPaintInfo; struct WPaintData; @@ -56,10 +94,22 @@ struct wmKeyMap; struct wmOperator; struct wmOperatorType; +/* +maximum symmetry passes returned by SCULPT_get_symmetry_pass. +enough for about ~30 radial symmetry passes, which seems like plenty + +used by various code that needs to statically store per-pass state. +*/ +#define SCULPT_MAX_SYMMETRY_PASSES 255 + +/* Updates */ + /* -------------------------------------------------------------------- */ /** \name Sculpt Types * \{ */ +enum { SCULPT_SHARP_SIMPLE, SCULPT_SHARP_PLANE }; + enum SculptUpdateType { SCULPT_UPDATE_COORDS = 1 << 0, SCULPT_UPDATE_MASK = 1 << 1, @@ -71,20 +121,26 @@ enum SculptUpdateType { struct SculptCursorGeometryInfo { blender::float3 location; + blender::float3 back_location; blender::float3 normal; blender::float3 active_vertex_co; }; -#define SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY 256 +struct _SculptNeighborRef { + PBVHVertRef vertex; + PBVHEdgeRef edge; +}; + +#define SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY 16 struct SculptVertexNeighborIter { /* Storage */ - PBVHVertRef *neighbors; + struct _SculptNeighborRef *neighbors; int *neighbor_indices; + int size; int capacity; - - PBVHVertRef neighbors_fixed[SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY]; + struct _SculptNeighborRef neighbors_fixed[SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY]; int neighbor_indices_fixed[SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY]; /* Internal iterator. */ @@ -92,26 +148,43 @@ struct SculptVertexNeighborIter { int i; /* Public */ - int index; PBVHVertRef vertex; + PBVHEdgeRef edge; + int index; + bool has_edge; // does this iteration step have an edge, fake neighbors do not bool is_duplicate; + bool no_free; +}; + +struct SculptFaceSetIsland { + PBVHFaceRef *faces; + int totface; +}; + +struct SculptFaceSetIslands { + SculptFaceSetIsland *islands; + int totisland; }; /* Sculpt Original Data */ struct SculptOrigVertData { BMLog *bm_log; + blender::ed::sculpt_paint::undo::Type datatype; blender::ed::sculpt_paint::undo::Node *unode; float (*coords)[3]; float (*normals)[3]; const float *vmasks; float (*colors)[4]; + float _no[3]; /* Original coordinate, normal, and mask. */ const float *co; const float *no; float mask; const float *col; + struct PBVH *pbvh; + struct SculptSession *ss; }; struct SculptOrigFaceData { @@ -134,19 +207,6 @@ enum eBoundaryAutomaskMode { namespace blender::ed::sculpt_paint::undo { -enum class Type { - Position, - HideVert, - HideFace, - Mask, - DyntopoBegin, - DyntopoEnd, - DyntopoSymmetrize, - Geometry, - FaceSet, - Color, -}; - /* Storage of geometry for the undo node. * Is used as a storage for either original or modified geometry. */ struct NodeGeometry { @@ -167,11 +227,18 @@ struct NodeGeometry { }; struct Node { + struct Node *next, *prev; + Type type; char idname[MAX_ID_NAME]; /* Name instead of pointer. */ void *node; /* only during push, not valid afterwards! */ + int verts_num; + int corners_num; + int faces_num; + int grids_num; + Array position; Array orig_position; Array normal; @@ -203,6 +270,7 @@ struct Node { /* bmesh */ BMLogEntry *bm_entry; + BMLog *bm_log; bool applied; /* shape keys */ @@ -218,9 +286,6 @@ struct Node { undo::NodeGeometry geometry_original; undo::NodeGeometry geometry_modified; - /* Geometry at the bmesh enter moment. */ - undo::NodeGeometry geometry_bmesh_enter; - /* pivot */ float3 pivot_pos; float pivot_rot[4]; @@ -228,12 +293,42 @@ struct Node { /* Sculpt Face Sets */ Array face_sets; + // dyntopo stuff + struct NodeUndoKey { + int node_id; + Type type; + + uint64_t hash() const + { + return node_id + (int(type) << 24); + } + + bool operator==(const NodeUndoKey &b) const + { + return node_id == b.node_id && type == b.type; + } + bool operator!=(const NodeUndoKey &b) const + { + return !operator==(b); + } + }; + + /* Set of Dyntopo nodes, indexed by node id and type, that've + * already been undo pushed. + */ + ::blender::Set dyntopo_undo_set; + int typemask; + Vector face_indices; size_t undo_size; + // int gen, lasthash; }; -} +} // namespace blender::ed::sculpt_paint::undo + +// XXX +//using namespace blender::ed::sculpt_paint; /* Factor of brush to have rake point following behind * (could be configurable but this is reasonable default). */ @@ -256,6 +351,12 @@ struct SculptBrushTest { int radial_symmetry_pass; blender::float4x4 symm_rot_mat_inv; + float tip_roundness; + float tip_scale_x; + bool test_cube_z; + + float cube_matrix[4][4]; + /* For circle (not sphere) projection. */ float plane_view[4]; @@ -264,10 +365,32 @@ struct SculptBrushTest { /* View3d clipping - only set rv3d for clipping */ RegionView3D *clip_rv3d; + char falloff_shape; }; using SculptBrushTestFn = bool (*)(SculptBrushTest *test, const float co[3]); +struct SculptSearchSphereData { + const Sculpt *sd; + SculptSession *ss; + float radius_squared; + const float *center; + bool original; + /* This ignores fully masked and fully hidden nodes. */ + bool ignore_fully_ineffective; + struct Object *ob; + struct Brush *brush; +}; + +struct SculptSearchCircleData { + Sculpt *sd; + SculptSession *ss; + float radius_squared; + bool original; + bool ignore_fully_ineffective; + DistRayAABB_Precalc *dist_ray_to_aabb_precalc; +}; + /* Sculpt Filters */ enum SculptFilterOrientation { SCULPT_FILTER_ORIENTATION_LOCAL = 0, @@ -284,6 +407,8 @@ enum SculptTransformDisplacementMode { }; #define SCULPT_CLAY_STABILIZER_LEN 10 +#define SCULPT_SPEED_MA_SIZE 4 +#define GRAB_DELTA_MA_SIZE 3 namespace blender::ed::sculpt_paint { @@ -294,12 +419,11 @@ struct Cache { bool enabled_force_axis[3]; int random_seed; - /* Used for alternating between filter operations in filters that need to apply different ones to - * achieve certain effects. */ + /* Used for alternating between filter operations in filters that need to apply different ones + * to achieve certain effects. */ int iteration_count; /* Stores the displacement produced by the laplacian step of HC smooth. */ - float (*surface_smooth_laplacian_disp)[3]; float surface_smooth_shape_preservation; float surface_smooth_current_vertex; @@ -310,6 +434,10 @@ struct Cache { float *sharpen_factor; float (*detail_directions)[3]; + /* Sphere mesh filter. */ + float sphere_center[3]; + float sphere_radius; + /* Filter orientation. */ SculptFilterOrientation orientation; float4x4 obmat; @@ -341,13 +469,27 @@ struct Cache { int active_face_set; + /* Transform. */ SculptTransformDisplacementMode transform_displacement_mode; std::unique_ptr automasking; float3 initial_normal; float3 view_normal; - /* Pre-smoothed colors used by sharpening. Colors are HSL. */ + /* Mask Filter. */ + int mask_filter_current_step; + float *mask_filter_ref; + + GHash *mask_delta_step; + + bool preserve_fset_boundaries; + bool weighted_smooth; + float hard_edge_fac; + bool hard_edge_mode; + float hard_corner_pin; + float bound_smooth_radius; + float bevel_smooth_fac; + float (*pre_smoothed_color)[4]; ViewContext vc; @@ -355,7 +497,7 @@ struct Cache { bool no_orig_co; }; -} +} // namespace filter /** * This structure contains all the temporary data @@ -371,13 +513,13 @@ struct StrokeCache { float2 initial_mouse; /* Variants */ + float last_anchored_radius; /* Used by paint_mesh_restore_co. */ float radius; float radius_squared; float3 true_location; float3 true_last_location; float3 location; float3 last_location; - float stroke_distance; /* Used for alternating between deformation in brushes that need to apply different ones to * achieve certain effects. */ @@ -424,11 +566,20 @@ struct StrokeCache { float3 grab_delta, grab_delta_symmetry; float3 old_grab_location, orig_grab_location; + // next_grab_delta is same as grab_delta except in smooth rake mode + float prev_grab_delta[3], next_grab_delta[3]; + float prev_grab_delta_symmetry[3], next_grab_delta_symmetry[3]; + float grab_delta_avg[GRAB_DELTA_MA_SIZE][3]; + int grab_delta_avg_cur; + /* screen-space rotation defined by mouse motion */ float rake_rotation[4], rake_rotation_symmetry[4]; bool is_rake_rotation_valid; SculptRakeData rake_data; + /* Geodesic distances. */ + float *geodesic_dists[PAINT_SYMM_AREAS]; + /* Face Sets */ int paint_face_set; @@ -440,12 +591,17 @@ struct StrokeCache { float3 true_view_normal; float3 view_normal; + float view_origin[3]; + float true_view_origin[3]; + /* sculpt_normal gets calculated by calc_sculpt_normal(), then the * sculpt_normal_symm gets updated quickly with the usual symmetry * transforms */ float3 sculpt_normal; float3 sculpt_normal_symm; + float cached_area_normal[3]; + /* Used for area texture mode, local_mat gets calculated by * calc_brush_local_mat() and used in sculpt_apply_texture(). * Transforms from model-space coords to local area coords. @@ -468,6 +624,8 @@ struct StrokeCache { float3 anchored_location; + /* Fairing. */ + /* Paint Brush. */ struct { float hardness; @@ -480,9 +638,6 @@ struct StrokeCache { /* Pose brush */ SculptPoseIKChain *pose_ik_chain; - /* Enhance Details. */ - float (*detail_directions)[3]; - /* Clay Thumb brush */ /* Angle of the front tilting plane of the brush to simulate clay accumulation. */ float clay_thumb_front_angle; @@ -500,13 +655,6 @@ struct StrokeCache { /* Boundary brush */ SculptBoundary *boundaries[PAINT_SYMM_AREAS]; - /* Surface Smooth Brush */ - /* Stores the displacement produced by the laplacian step of HC smooth. */ - float (*surface_smooth_laplacian_disp)[3]; - - /* Layer brush */ - float *layer_displacement_factor; - float vertex_rotation; /* amount to rotate the vertices when using rotate brush */ Dial *dial; @@ -515,6 +663,10 @@ struct StrokeCache { int saved_smooth_size; /* smooth tool copies the size of the current tool */ bool alt_smooth; + /* Scene Project Brush */ + struct SnapObjectContext *snap_context; + struct Depsgraph *depsgraph; + float plane_trim_squared; bool supports_gravity; @@ -532,6 +684,35 @@ struct StrokeCache { rcti previous_r; /* previous redraw rectangle */ rcti current_r; /* current redraw rectangle */ + float stroke_distance; // copy of PaintStroke->stroke_distance + float stroke_distance_t; // copy of PaintStroke->stroke_distance_t + float stroke_spacing_t; + float last_stroke_distance_t; + + float last_dyntopo_t; + float last_dyntopo_nodesplit_t; + float last_smooth_t[SCULPT_MAX_SYMMETRY_PASSES]; + float last_rake_t[SCULPT_MAX_SYMMETRY_PASSES]; + + struct PaintStroke *stroke; + struct bContext *C; + + struct BrushCommandList *commandlist; + bool use_plane_trim; + + struct NeighborCache *ncache; + float speed_avg[SCULPT_SPEED_MA_SIZE]; // moving average for speed + int speed_avg_cur; + double last_speed_time; + + // if nonzero, override brush sculpt tool + int tool_override; + + float mouse_cubic[4][3]; + float world_cubic[4][3]; + float world_cubic_arclength; + float mouse_cubic_arclength; + bool has_cubic; int stroke_id; }; @@ -583,10 +764,12 @@ struct Cache { float *face_falloff; float max_face_falloff; - /* Falloff value of the active element (vertex or base mesh face) that Expand will expand to. */ + /* Falloff value of the active element (vertex or base mesh face) that Expand will expand to. + */ float active_falloff; - /* When set to true, expand skips all falloff computations and considers all elements as enabled. + /* When set to true, expand skips all falloff computations and considers all elements as + * enabled. */ bool all_enabled; @@ -595,7 +778,6 @@ struct Cache { float2 initial_mouse_move; float2 initial_mouse; PBVHVertRef initial_active_vertex; - int initial_active_vertex_i; int initial_active_face_set; /* Maximum number of vertices allowed in the SculptSession for previewing the falloff using @@ -650,6 +832,9 @@ struct Cache { /* When set to true, preserves the previous state of the data and adds the new one on top. */ bool preserve; + /* When true, preserve mode will flip in inverse mode */ + bool preserve_flip_inverse; + /* When set to true, the mask or colors will be applied as a gradient. */ bool falloff_gradient; @@ -668,8 +853,8 @@ struct Cache { * instead of creating a new one. */ bool modify_active_face_set; - /* When set to true, Expand will reposition the sculpt pivot to the boundary of the expand result - * after finishing the operation. */ + /* When set to true, Expand will reposition the sculpt pivot to the boundary of the expand + * result after finishing the operation. */ bool reposition_pivot; /* If nothing is masked set mask of every vertex to 0. */ @@ -680,7 +865,7 @@ struct Cache { short blend_mode; /* Face Sets at the first step of the expand operation, before starting modifying the active - * vertex and active falloff. These are not the original Face Sets of the sculpt before starting + * vertex and active falloff. These are not the original Face Sets of the sculpt before * the operator as they could have been modified by Expand when initializing the operator and * before starting changing the active vertex. These Face Sets are used for restoring and * checking the Face Sets state while the Expand operation modal runs. */ @@ -695,11 +880,39 @@ struct Cache { int normal_falloff_blur_steps; }; -} +} // namespace expand -} +} // namespace blender::ed::sculpt_paint -/** \} */ +struct MaskFilterDeltaStep { + int totelem; + int *index; + float *delta; +}; + +struct SculptCurvatureData { + float ks[3]; + float principle[3][3]; // normalized +}; + +struct SculptFaceSetDrawData { + struct Sculpt *sd; + struct Object *ob; + blender::Span nodes; + struct Brush *brush; + float bstrength; + + int faceset; + int count; + bool use_fset_curve; + bool use_fset_strength; + + float *prev_stroke_direction; + float *stroke_direction; + float *next_stroke_direction; + struct CurveMapping *curve; + int iteration; +}; /** \} */ @@ -735,6 +948,11 @@ bool SCULPT_handles_colors_report(SculptSession *ss, ReportList *reports); void SCULPT_flush_update_step(bContext *C, SculptUpdateType update_flags); void SCULPT_flush_update_done(const bContext *C, Object *ob, SculptUpdateType update_flags); +enum PBVHClearFlags { + PBVH_CLEAR_CACHE_PBVH = 1 << 1, + PBVH_CLEAR_FREE_BMESH = 1 << 2, +}; + void SCULPT_pbvh_clear(Object *ob); /** @@ -773,14 +991,15 @@ bool SCULPT_stroke_get_location(bContext *C, const float mouse[2], bool force_original); /** - * Gets the normal, location and active vertex location of the geometry under the cursor. This also - * updates the active vertex and cursor related data of the SculptSession using the mouse position + * Gets the normal, location and active vertex location of the geometry under the cursor. This + * also updates the active vertex and cursor related data of the SculptSession using the mouse + * position */ bool SCULPT_cursor_geometry_info_update(bContext *C, SculptCursorGeometryInfo *out, const float mouse[2], bool use_sampled_normal); -void SCULPT_geometry_preview_lines_update(bContext *C, SculptSession *ss, float radius); +void SCULPT_geometry_preview_lines_update(bContext *C, struct SculptSession *ss, float radius); void SCULPT_stroke_modifiers_check(const bContext *C, Object *ob, const Brush *brush); float SCULPT_raycast_init(ViewContext *vc, @@ -802,8 +1021,8 @@ bool SCULPT_stroke_is_main_symmetry_pass(blender::ed::sculpt_paint::StrokeCache * Return true only once per stroke on the first symmetry pass, regardless of the symmetry passes * enabled. * - * This should be used for functionality that needs to be computed once per stroke of a particular - * tool (allocating memory, updating random seeds...). + * This should be used for functionality that needs to be computed once per stroke of a + * particular tool (allocating memory, updating random seeds...). */ bool SCULPT_stroke_is_first_brush_step(blender::ed::sculpt_paint::StrokeCache *cache); /** @@ -844,12 +1063,24 @@ inline void SCULPT_mask_vert_set(const PBVHType type, /** Ensure random access; required for PBVH_BMESH */ void SCULPT_vertex_random_access_ensure(SculptSession *ss); +/** Ensure random access; required for PBVH_BMESH */ +void SCULPT_face_random_access_ensure(SculptSession *ss); + +int SCULPT_vertex_valence_get(const SculptSession *ss, PBVHVertRef vertex); int SCULPT_vertex_count_get(const SculptSession *ss); + const float *SCULPT_vertex_co_get(const SculptSession *ss, PBVHVertRef vertex); +void SCULPT_vertex_co_set(SculptSession *ss, PBVHVertRef vertex, const float *co); +const float *SCULPT_vertex_origco_get(const SculptSession *ss, PBVHVertRef vertex); +void SCULPT_vertex_origno_get(const SculptSession *ss, PBVHVertRef vertex, float no[3]); /** Get the normal for a given sculpt vertex; do not modify the result */ void SCULPT_vertex_normal_get(const SculptSession *ss, PBVHVertRef vertex, float no[3]); +const float *SCULPT_vertex_persistent_co_get(SculptSession *ss, PBVHVertRef vertex); +void SCULPT_vertex_persistent_normal_get(SculptSession *ss, PBVHVertRef vertex, float no[3]); + +float SCULPT_vertex_mask_get(SculptSession *ss, PBVHVertRef vertex); float SCULPT_mask_get_at_grids_vert_index(const SubdivCCG &subdiv_ccg, const CCGKey &key, int vert_index); @@ -864,8 +1095,7 @@ bool SCULPT_has_colors(const SculptSession *ss); /** Returns true if the active color attribute is on loop (AttrDomain::Corner) domain. */ bool SCULPT_has_loop_colors(const Object *ob); -const float *SCULPT_vertex_persistent_co_get(SculptSession *ss, PBVHVertRef vertex); -void SCULPT_vertex_persistent_normal_get(SculptSession *ss, PBVHVertRef vertex, float no[3]); +bool SCULPT_has_persistent_base(SculptSession *ss); /** * Coordinates used for manipulating the base mesh when Grab Active Vertex is enabled. @@ -886,30 +1116,32 @@ float *SCULPT_brush_deform_target_vertex_co_get(SculptSession *ss, int deform_target, PBVHVertexIter *iter); -void SCULPT_vertex_neighbors_get(SculptSession *ss, - PBVHVertRef vertex, - bool include_duplicates, +void SCULPT_vertex_neighbors_get(const struct SculptSession *ss, + const PBVHVertRef vref, + const bool include_duplicates, SculptVertexNeighborIter *iter); /** Iterator over neighboring vertices. */ -#define SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN(ss, v_index, neighbor_iterator) \ - SCULPT_vertex_neighbors_get(ss, v_index, false, &neighbor_iterator); \ +#define SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN(ss, vertex_ref, neighbor_iterator) \ + SCULPT_vertex_neighbors_get(ss, vertex_ref, false, &neighbor_iterator); \ for (neighbor_iterator.i = 0; neighbor_iterator.i < neighbor_iterator.size; \ neighbor_iterator.i++) \ { \ - neighbor_iterator.vertex = neighbor_iterator.neighbors[neighbor_iterator.i]; \ + neighbor_iterator.vertex = neighbor_iterator.neighbors[neighbor_iterator.i].vertex; \ + neighbor_iterator.edge = neighbor_iterator.neighbors[neighbor_iterator.i].edge; \ neighbor_iterator.index = neighbor_iterator.neighbor_indices[neighbor_iterator.i]; /** * Iterate over neighboring and duplicate vertices (for PBVH_GRIDS). * Duplicates come first since they are nearest for flood-fill. */ -#define SCULPT_VERTEX_DUPLICATES_AND_NEIGHBORS_ITER_BEGIN(ss, v_index, neighbor_iterator) \ - SCULPT_vertex_neighbors_get(ss, v_index, true, &neighbor_iterator); \ +#define SCULPT_VERTEX_DUPLICATES_AND_NEIGHBORS_ITER_BEGIN(ss, vertex_ref, neighbor_iterator) \ + SCULPT_vertex_neighbors_get(ss, vertex_ref, true, &neighbor_iterator); \ for (neighbor_iterator.i = neighbor_iterator.size - 1; neighbor_iterator.i >= 0; \ neighbor_iterator.i--) \ { \ - neighbor_iterator.vertex = neighbor_iterator.neighbors[neighbor_iterator.i]; \ + neighbor_iterator.vertex = neighbor_iterator.neighbors[neighbor_iterator.i].vertex; \ + neighbor_iterator.edge = neighbor_iterator.neighbors[neighbor_iterator.i].edge; \ neighbor_iterator.index = neighbor_iterator.neighbor_indices[neighbor_iterator.i]; \ neighbor_iterator.is_duplicate = (neighbor_iterator.i >= \ neighbor_iterator.size - neighbor_iterator.num_duplicates); @@ -921,6 +1153,15 @@ void SCULPT_vertex_neighbors_get(SculptSession *ss, } \ ((void)0) +#define SCULPT_VERTEX_NEIGHBORS_ITER_FREE(neighbor_iterator) \ + if (neighbor_iterator.neighbors && !neighbor_iterator.no_free && \ + neighbor_iterator.neighbors != neighbor_iterator.neighbors_fixed) \ + { \ + MEM_freeN(neighbor_iterator.neighbors); \ + MEM_freeN(neighbor_iterator.neighbor_indices); \ + } \ + ((void)0) + PBVHVertRef SCULPT_active_vertex_get(SculptSession *ss); const float *SCULPT_active_vertex_co_get(SculptSession *ss); @@ -939,8 +1180,19 @@ void SCULPT_fake_neighbors_free(Object *ob); /* Vertex Info. */ void SCULPT_boundary_info_ensure(Object *object); + +/* Update all boundary and valence info in the mesh. */ +void SCULPT_update_all_valence_boundary(Object *ob); + /* Boundary Info needs to be initialized in order to use this function. */ -bool SCULPT_vertex_is_boundary(const SculptSession *ss, PBVHVertRef vertex); +eSculptCorner SCULPT_vertex_is_corner(const SculptSession *ss, + const PBVHVertRef index, + eSculptCorner cornertype); + +/* Boundary Info needs to be initialized in order to use this function. */ +eSculptBoundary SCULPT_vertex_is_boundary(const SculptSession *ss, + const PBVHVertRef index, + eSculptBoundary boundary_types); /** \} */ @@ -950,13 +1202,34 @@ bool SCULPT_vertex_is_boundary(const SculptSession *ss, PBVHVertRef vertex); namespace blender::ed::sculpt_paint { +/* Flags all the vertices of face for boundary update. For PBVH_GRIDS + * this includes all the verts in all the grids belonging to that face. + */ +void face_mark_boundary_update(SculptSession *ss, PBVHFaceRef face); + namespace hide { bool vert_visible_get(const SculptSession *ss, PBVHVertRef vertex); bool vert_all_faces_visible_get(const SculptSession *ss, PBVHVertRef vertex); bool vert_any_face_visible_get(SculptSession *ss, PBVHVertRef vertex); +void sync_all_from_faces(Object &object); +void face_set(SculptSession *ss, int face_set, bool visible); -} +} // namespace hide + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Face API + * \{ */ + +SculptFaceSetIslands *SCULPT_face_set_islands_get(SculptSession *ss, int fset); +void SCULPT_face_set_islands_free(SculptSession *ss, SculptFaceSetIslands *islands); + +SculptFaceSetIsland *SCULPT_face_set_island_get(SculptSession *ss, PBVHFaceRef face, int fset); +void SCULPT_face_set_island_free(SculptFaceSetIsland *island); + +void face_normal_get(const Object *ob, PBVHFaceRef face, float no[3]); /** \} */ @@ -968,17 +1241,29 @@ namespace face_set { int active_face_set_get(SculptSession *ss); int vert_face_set_get(SculptSession *ss, PBVHVertRef vertex); +void vert_face_set_set(SculptSession *ss, PBVHVertRef vertex, int face_set); bool vert_has_face_set(SculptSession *ss, PBVHVertRef vertex, int face_set); bool vert_has_unique_face_set(SculptSession *ss, PBVHVertRef vertex); bke::SpanAttributeWriter ensure_face_sets_mesh(Object &object); int ensure_face_sets_bmesh(Object &object); -Array duplicate_face_sets(const Mesh &mesh); +Array duplicate_face_sets(Object &object); -} +void visibility_all_invert(SculptSession *ss); +void visibility_set(SculptSession *ss, int face_set, bool visible); +void visibility_all_set(Object *ob, bool visible); +} // namespace face_set -} +} // namespace blender::ed::sculpt_paint + +int SCULPT_face_set_original_get(SculptSession *ss, PBVHFaceRef face); + +int SCULPT_face_set_get(const SculptSession *ss, PBVHFaceRef face); +void SCULPT_face_set_set(SculptSession *ss, PBVHFaceRef face, int fset); + +bool SCULPT_face_select_get(SculptSession *ss, PBVHFaceRef face); +bool SCULPT_face_is_hidden(const SculptSession *ss, PBVHFaceRef face); /** \} */ @@ -987,6 +1272,7 @@ Array duplicate_face_sets(const Mesh &mesh); * \{ */ /** + * DEPRECATED: use SCULPT_vertex_check_origdata and SCULPT_vertex_get_sculptvert * Initialize a #SculptOrigVertData for accessing original vertex data; * handles #BMesh, #Mesh, and multi-resolution. */ @@ -995,10 +1281,33 @@ void SCULPT_orig_vert_data_init(SculptOrigVertData *data, PBVHNode *node, blender::ed::sculpt_paint::undo::Type type); /** + * DEPRECATED: use SCULPT_vertex_check_origdata and SCULPT_vertex_get_sculptvert * Update a #SculptOrigVertData for a particular vertex from the PBVH iterator. */ -void SCULPT_orig_vert_data_update(SculptOrigVertData *orig_data, PBVHVertexIter *iter); +void SCULPT_orig_vert_data_update(SculptOrigVertData *orig_data, PBVHVertRef vertex); + +void SCULPT_face_check_origdata(SculptSession *ss, PBVHFaceRef face); +bool SCULPT_vertex_check_origdata(SculptSession *ss, PBVHVertRef vertex); + +/** check that original face set values are up to date + * TODO: rename to SCULPT_face_set_original_ensure + */ +void SCULPT_face_ensure_original(SculptSession *ss, struct Object *ob); + /** + * Initialize a #SculptOrigFaceData for accessing original face data; + * handles #BMesh, #Mesh, and multi-resolution. + */ +void SCULPT_orig_face_data_init(SculptOrigFaceData *data, + Object *ob, + PBVHNode *node, + blender::ed::sculpt_paint::undo::Type type); +/** + * Update a #SculptOrigFaceData for a particular vertex from the PBVH iterator. + */ +void SCULPT_orig_face_data_update(SculptOrigFaceData *orig_data, PBVHFaceIter *iter); +/** + * DEPRECATED: use SCULPT_vertex_check_origdata and SCULPT_vertex_get_sculptvert * Initialize a #SculptOrigVertData for accessing original vertex data; * handles #BMesh, #Mesh, and multi-resolution. */ @@ -1072,16 +1381,29 @@ void SCULPT_flip_quat_by_symm_area(float quat[4], const float pivot[3]); /** - * Initialize a point-in-brush test + * Initialize a point-in-brush test with a given falloff shape. + * + * \param falloff_shape: #PAINT_FALLOFF_SHAPE_SPHERE, #PAINT_FALLOFF_SHAPE_TUBE or + * #PAINT_FALLOFF_SHAPE_NOOP \return The brush falloff function, or nullptr if falloff_shape was + * #PAINT_FALLOFF_SHAPE_NOOP */ -void SCULPT_brush_test_init(SculptSession *ss, SculptBrushTest *test); + +SculptBrushTestFn SCULPT_brush_test_init(const SculptSession *ss, SculptBrushTest *test); +SculptBrushTestFn SCULPT_brush_test_init_ex(const SculptSession *ss, + SculptBrushTest *test, + char falloff_shape, + float tip_roundness, + float tip_scale_x); +bool SCULPT_brush_test(SculptBrushTest *test, const float co[3]); bool SCULPT_brush_test_sphere_sq(SculptBrushTest *test, const float co[3]); bool SCULPT_brush_test_cube(SculptBrushTest *test, const float co[3], const float local[4][4], const float roundness, - const float tip_scale_x); + const float tip_scale_x, + bool test_z); + bool SCULPT_brush_test_circle_sq(SculptBrushTest *test, const float co[3]); namespace blender::ed::sculpt_paint { @@ -1093,7 +1415,7 @@ bool node_in_cylinder(const DistRayAABB_Precalc &dist_ray_precalc, float radius_sq, bool original); -} +} // namespace blender::ed::sculpt_paint void SCULPT_combine_transform_proxies(Sculpt *sd, Object *ob); @@ -1104,10 +1426,10 @@ void SCULPT_combine_transform_proxies(Sculpt *sd, Object *ob); * \return The brush falloff function. */ -SculptBrushTestFn SCULPT_brush_test_init_with_falloff_shape(SculptSession *ss, +SculptBrushTestFn SCULPT_brush_test_init_with_falloff_shape(const SculptSession *ss, SculptBrushTest *test, char falloff_shape); -const float *SCULPT_brush_frontface_normal_from_falloff_shape(SculptSession *ss, +const float *SCULPT_brush_frontface_normal_from_falloff_shape(const SculptSession *ss, char falloff_shape); void SCULPT_cube_tip_init(Sculpt *sd, Object *ob, Brush *brush, float mat[4][4]); @@ -1176,7 +1498,7 @@ void add_active(Object *ob, SculptSession *ss, SculptFloodFill *flood, float rad void add_initial_with_symmetry( Object *ob, SculptSession *ss, SculptFloodFill *flood, PBVHVertRef vertex, float radius); void add_initial(SculptFloodFill *flood, PBVHVertRef vertex); -void add_and_skip_initial(SculptFloodFill *flood, PBVHVertRef vertex); +void add_and_skip_initial(SculptSession *ss, SculptFloodFill *flood, PBVHVertRef vertex); void execute(SculptSession *ss, SculptFloodFill *flood, bool (*func)(SculptSession *ss, @@ -1186,7 +1508,7 @@ void execute(SculptSession *ss, void *userdata), void *userdata); -} +} // namespace blender::ed::sculpt_paint::flood_fill /** \} */ @@ -1196,12 +1518,7 @@ void execute(SculptSession *ss, namespace blender::ed::sculpt_paint::dyntopo { -enum WarnFlag { - VDATA = (1 << 0), - EDATA = (1 << 1), - LDATA = (1 << 2), - MODIFIER = (1 << 3), -}; +enum WarnFlag { MODIFIER = (1 << 0), MULTIRES = (1 << 1) }; ENUM_OPERATORS(WarnFlag, MODIFIER); /** Enable dynamic topology; mesh will be triangulated */ @@ -1217,9 +1534,7 @@ void disable_with_undo(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object * * Others, like smooth, are better without. * Same goes for alt-key smoothing. */ -bool stroke_is_dyntopo(const SculptSession *ss, const Brush *brush); - -void triangulate(BMesh *bm); +bool stroke_is_dyntopo(const SculptSession *ss, const Sculpt *sd, const Brush *brush); WarnFlag check_attribute_warning(Scene *scene, Object *ob); @@ -1267,8 +1582,8 @@ float constant_to_relative_detail(const float constant_detail, const float pixel_radius, const float pixel_size, const Object *ob); -} -} +} // namespace detail_size +} // namespace blender::ed::sculpt_paint::dyntopo /** \} */ @@ -1302,6 +1617,7 @@ struct Cache { }; struct NodeData { + SculptSession *ss; SculptOrigVertData orig_data; bool have_orig_data; }; @@ -1333,6 +1649,7 @@ Cache *active_cache_get(SculptSession *ss); std::unique_ptr cache_init(const Sculpt *sd, Object *ob); std::unique_ptr cache_init(const Sculpt *sd, const Brush *brush, Object *ob); void cache_free(Cache *automasking); +bool needs_original(Sculpt *sd, Brush *brush); bool mode_enabled(const Sculpt *sd, const Brush *br, eAutomasking_flag mode); bool is_enabled(const Sculpt *sd, const SculptSession *ss, const Brush *br); @@ -1342,7 +1659,7 @@ int settings_hash(const Object &ob, const Cache &automasking); bool tool_can_reuse_automask(int sculpt_tool); -} +} // namespace blender::ed::sculpt_paint::auto_mask /** \} */ @@ -1353,15 +1670,18 @@ bool tool_can_reuse_automask(int sculpt_tool); namespace blender::ed::sculpt_paint::geodesic { /** - * Returns an array indexed by vertex index containing the geodesic distance to the closest vertex - * in the initial vertex set. The caller is responsible for freeing the array. - * Geodesic distances will only work when used with PBVH_FACES, for other types of PBVH it will - * fallback to euclidean distances to one of the initial vertices in the set. + * Returns an array indexed by vertex index containing the geodesic distance to the closest + * vertex in the initial vertex set. The caller is responsible for freeing the array. Geodesic + * distances will only work when used with PBVH_FACES, for other types of PBVH it will fallback + * to euclidean distances to one of the initial vertices in the set. */ -float *distances_create(Object *ob, GSet *initial_verts, float limit_radius); +float *distances_create(Object *ob, + GSet *initial_verts, + float limit_radius, + PBVHVertRef *r_closest_verts = nullptr, + blender::Span vertco_override = {}); float *distances_create_from_vert_and_symm(Object *ob, PBVHVertRef vertex, float limit_radius); - -} +} // namespace blender::ed::sculpt_paint::geodesic /** \} */ @@ -1378,7 +1698,7 @@ void cache_init(bContext *C, const float mval_fl[2], float area_normal_radius, float start_strength); -void cache_free(SculptSession *ss); +void cache_free(SculptSession *ss, Object *ob); void register_operator_props(wmOperatorType *ot); /* Filter orientation utils. */ @@ -1386,7 +1706,7 @@ void to_orientation_space(float r_v[3], filter::Cache *filter_cache); void to_object_space(float r_v[3], filter::Cache *filter_cache); void zero_disabled_axis_components(float r_v[3], filter::Cache *filter_cache); -} +} // namespace blender::ed::sculpt_paint::filter /** \} */ @@ -1512,6 +1832,7 @@ void ensure_nodes_constraints(Sculpt *sd, /** * Cursor drawing function. */ + void simulation_limits_draw(uint gpuattr, const Brush *brush, const float location[3], @@ -1529,7 +1850,7 @@ Vector brush_affected_nodes_gather(SculptSession *ss, Brush *brush); bool is_cloth_deform_brush(const Brush *brush); -} +} // namespace blender::ed::sculpt_paint::cloth /** \} */ @@ -1543,47 +1864,79 @@ namespace blender::ed::sculpt_paint::smooth { * For bmesh: Average surrounding verts based on an orthogonality measure. * Naturally converges to a quad-like structure. */ -void bmesh_four_neighbor_average(float avg[3], float direction[3], BMVert *v); -void neighbor_coords_average(SculptSession *ss, float result[3], PBVHVertRef vertex); -float neighbor_mask_average(SculptSession *ss, SculptMaskWriteInfo write_info, PBVHVertRef vertex); -void neighbor_color_average(SculptSession *ss, float result[4], PBVHVertRef vertex); +void bmesh_four_neighbor_average(SculptSession *ss, + float avg[3], + float direction[3], + BMVert *v, + float projection, + float hard_corner_pin, + int cd_temp, + bool weighted, + bool do_origco, + float factor, + bool reproject_uvs); -/** - * Mask the mesh boundaries smoothing only the mesh surface without using auto-masking. - */ -void neighbor_coords_average_interior(SculptSession *ss, float result[3], PBVHVertRef vertex); +void neighbor_coords_average(SculptSession *ss, + float result[3], + PBVHVertRef index, + float projection, + float hard_corner_pin, + bool weighted, + float factor = 1.0f); +float neighbor_mask_average(SculptSession *ss, + const SculptMaskWriteInfo write_info, + PBVHVertRef index); +void neighbor_color_average(SculptSession *ss, float result[4], PBVHVertRef index); -void do_smooth_brush(Sculpt *sd, Object *ob, Span nodes, float bstrength); -void do_smooth_brush(Sculpt *sd, Object *ob, Span nodes); +/* Mask the mesh boundaries smoothing only the mesh surface without using automasking. */ -void do_smooth_mask_brush(Sculpt *sd, Object *ob, Span nodes, float bstrength); +void neighbor_coords_average_interior(SculptSession *ss, + float result[3], + PBVHVertRef vertex, + float projection, + float hard_corner_pin, + bool use_area_weights, + bool smooth_origco = false, + float factor = 1.0f); + +BLI_INLINE eAttrCorrectMode need_reproject(const SculptSession *ss) +{ + return ss->bm ? ss->distort_correction_mode : UNDISTORT_NONE; +} + +/** \} */ +void smooth_undo_push(Sculpt *sd, Object *ob, Span nodes, Brush *brush); + +void smooth( + Sculpt *sd, Object *ob, Span nodes, float bstrength, const bool smooth_mask = 0.0); +void do_smooth_brush( + Sculpt *sd, Object *ob, Span nodes, float bstrength, const bool smooth_mask = 0.0); /* Surface Smooth Brush. */ +void surface_smooth_laplacian_init(Object *ob); void surface_smooth_laplacian_step(SculptSession *ss, float *disp, const float co[3], - float (*laplacian_disp)[3], - PBVHVertRef vertex, + const PBVHVertRef vertex, const float origco[3], - float alpha); -void surface_smooth_displace_step(SculptSession *ss, - float *co, - float (*laplacian_disp)[3], - PBVHVertRef vertex, - float beta, - float fade); -void do_surface_smooth_brush(Sculpt *sd, Object *ob, Span nodes); + const float alpha, + bool use_area_weights); +void surface_smooth_displace_step( + SculptSession *ss, float *co, const PBVHVertRef vertex, const float beta, const float fade); +void do_surface_smooth_brush(Sculpt *sd, Object *ob, blender::Span nodes); /* Slide/Relax */ void relax_vertex(SculptSession *ss, PBVHVertexIter *vd, float factor, - bool filter_boundary_face_sets, + eSculptBoundary boundary_mask, float *r_final_pos); -} +void smooth_undo_push(Sculpt *sd, Object *ob, Span nodes, Brush *brush); + +} // namespace blender::ed::sculpt_paint::smooth /** \} */ @@ -1628,7 +1981,19 @@ void push_begin_ex(Object *ob, const char *name); void push_end(Object *ob); void push_end_ex(Object *ob, const bool use_nested_undo); -} +void ensure_bmlog(Object *ob); +/** Updates BMLog for undo types `type` and `extraType`, + * this is done only once per stroke unless `type`/`extraType` match + * `force_undo_mask.` + */ +bool ensure_dyntopo_node_undo(Object *ob, + PBVHNode *node, + undo::Type type, + undo::Type extraType = undo::Type::None, + undo::Type force_push_mask = undo::Type::Position | + undo::Type::Color | undo::Type::Mask); + +} // namespace blender::ed::sculpt_paint::undo /** \} */ @@ -1650,7 +2015,7 @@ namespace blender::ed::sculpt_paint::expand { void SCULPT_OT_expand(wmOperatorType *ot); void modal_keymap(wmKeyConfig *keyconf); -} +} // namespace blender::ed::sculpt_paint::expand /** \} */ @@ -1773,7 +2138,7 @@ void operator_properties(wmOperatorType *ot); /* Apply the gesture action to the selected nodes. */ void apply(bContext &C, GestureData &gesture_data, wmOperator &op); -} +} // namespace blender::ed::sculpt_paint::gesture namespace blender::ed::sculpt_paint::project { void SCULPT_OT_project_line_gesture(wmOperatorType *ot); @@ -1782,7 +2147,7 @@ void SCULPT_OT_project_line_gesture(wmOperatorType *ot); namespace blender::ed::sculpt_paint::trim { void SCULPT_OT_trim_lasso_gesture(wmOperatorType *ot); void SCULPT_OT_trim_box_gesture(wmOperatorType *ot); -} +} // namespace blender::ed::sculpt_paint::trim /** \} */ @@ -1791,7 +2156,6 @@ void SCULPT_OT_trim_box_gesture(wmOperatorType *ot); * \{ */ namespace blender::ed::sculpt_paint::face_set { - void SCULPT_OT_face_sets_randomize_colors(wmOperatorType *ot); void SCULPT_OT_face_set_change_visibility(wmOperatorType *ot); void SCULPT_OT_face_sets_init(wmOperatorType *ot); @@ -1800,7 +2164,7 @@ void SCULPT_OT_face_sets_edit(wmOperatorType *ot); void SCULPT_OT_face_set_lasso_gesture(wmOperatorType *ot); void SCULPT_OT_face_set_box_gesture(wmOperatorType *ot); -} +} // namespace blender::ed::sculpt_paint::face_set /** \} */ /* -------------------------------------------------------------------- */ @@ -1819,7 +2183,7 @@ namespace blender::ed::sculpt_paint::filter { void SCULPT_OT_mesh_filter(wmOperatorType *ot); wmKeyMap *modal_keymap(wmKeyConfig *keyconf); -} +} // namespace blender::ed::sculpt_paint::filter namespace blender::ed::sculpt_paint::cloth { void SCULPT_OT_cloth_filter(wmOperatorType *ot); @@ -1832,7 +2196,7 @@ void SCULPT_OT_color_filter(wmOperatorType *ot); /** \} */ /* -------------------------------------------------------------------- */ -/** \name Interactive Mask Operators +/** \name Mask Operators * \{ */ namespace blender::ed::sculpt_paint::mask { @@ -1840,7 +2204,7 @@ namespace blender::ed::sculpt_paint::mask { void SCULPT_OT_mask_filter(wmOperatorType *ot); void SCULPT_OT_mask_init(wmOperatorType *ot); -} +} // namespace blender::ed::sculpt_paint::mask /** \} */ @@ -1852,12 +2216,18 @@ void SCULPT_OT_mask_init(wmOperatorType *ot); namespace blender::ed::sculpt_paint::dyntopo { +void apply_settings(Scene *scene, SculptSession *ss, Sculpt *sculpt, Brush *brush); + void SCULPT_OT_detail_flood_fill(wmOperatorType *ot); void SCULPT_OT_sample_detail_size(wmOperatorType *ot); void SCULPT_OT_dyntopo_detail_size_edit(wmOperatorType *ot); +/** \} */ + +/* Dyntopo. */ + void SCULPT_OT_dynamic_topology_toggle(wmOperatorType *ot); -} +} // namespace blender::ed::sculpt_paint::dyntopo /** \} */ @@ -1892,7 +2262,7 @@ SculptPoseIKChain *ik_chain_init( Object *ob, SculptSession *ss, Brush *br, const float initial_location[3], float radius); void ik_chain_free(SculptPoseIKChain *ik_chain); -} +} // namespace blender::ed::sculpt_paint::pose namespace blender::ed::sculpt_paint::boundary { @@ -1902,6 +2272,7 @@ namespace blender::ed::sculpt_paint::boundary { */ SculptBoundary *data_init(Object *object, Brush *brush, PBVHVertRef initial_vertex, float radius); void data_free(SculptBoundary *boundary); + /* Main Brush Function. */ void do_boundary_brush(Sculpt *sd, Object *ob, blender::Span nodes); @@ -1911,7 +2282,7 @@ void edges_preview_draw(uint gpuattr, float outline_alpha); void pivot_line_preview_draw(uint gpuattr, SculptSession *ss); -} +} // namespace blender::ed::sculpt_paint::boundary /* Multi-plane Scrape Brush. */ /* Main Brush Function. */ @@ -1933,14 +2304,15 @@ void do_draw_face_sets_brush(Sculpt *sd, Object *ob, Span nodes); namespace color { void do_paint_brush(PaintModeSettings *paint_mode_settings, + Scene *scene, Sculpt *sd, Object *ob, Span nodes, Span texnodes); void do_smear_brush(Sculpt *sd, Object *ob, Span nodes); -} +} // namespace color -} +} // namespace blender::ed::sculpt_paint /** * \brief Get the image canvas for painting on the given object. * @@ -2002,6 +2374,145 @@ void SCULPT_OT_brush_stroke(wmOperatorType *ot); } +eSculptBoundary SCULPT_edge_is_boundary(const SculptSession *ss, + const PBVHEdgeRef edge, + eSculptBoundary typemask); +void SCULPT_edge_get_verts(const SculptSession *ss, + const PBVHEdgeRef edge, + PBVHVertRef *r_v1, + PBVHVertRef *r_v2); +PBVHVertRef SCULPT_edge_other_vertex(const SculptSession *ss, + const PBVHEdgeRef edge, + const PBVHVertRef vertex); + +// #define SCULPT_REPLAY +#ifdef SCULPT_REPLAY +struct SculptReplayLog; +struct SculptBrushSample; + +# ifdef WIN32 +# define REPLAY_EXPORT __declspec(dllexport) +# else +# define REPLAY_EXPORT +# endif + +void SCULPT_replay_log_free(struct SculptReplayLog *log); +struct SculptReplayLog *SCULPT_replay_log_create(); +void SCULPT_replay_log_end(); +void SCULPT_replay_log_start(); +char *SCULPT_replay_serialize(); +void SCULPT_replay_log_append(struct Sculpt *sd, struct SculptSession *ss, struct Object *ob); +void SCULPT_replay_test(void); + +#endif + +struct BMesh *SCULPT_dyntopo_empty_bmesh(); + +/* initializes customdata layer used by SCULPT_neighbor_coords_average_interior when bound_smooth + * > 0.0f*/ +void SCULPT_bound_smooth_ensure(SculptSession *ss, Object *ob); + +/** \} */ + +int SCULPT_get_tool(const SculptSession *ss, const struct Brush *br); + +/* Sculpt API to get brush channel data +If ss->cache exists then ss->cache->channels_final +will be used, otherwise brush and tool settings channels +will be used (taking inheritence into account). +*/ + +/* -------------------------------------------------------------------- */ +/** \name Brush channel accessor API + * \{ */ + +/** Get brush channel value. The channel will be + fetched from ss->cache->channels_final. If + ss->cache is NULL, channel will be fetched + from sd->channels and br->channels taking + inheritance flags into account. + + Note that sd or br may be NULL, but not + both.*/ +float SCULPT_get_float_intern(const SculptSession *ss, + const char *idname, + const Sculpt *sd, + const Brush *br); +#define SCULPT_get_float(ss, idname, sd, br) \ + SCULPT_get_float_intern(ss, BRUSH_BUILTIN_##idname, sd, br) + +int SCULPT_get_int_intern(const SculptSession *ss, + const char *idname, + const Sculpt *sd, + const Brush *br); +#define SCULPT_get_int(ss, idname, sd, br) \ + SCULPT_get_int_intern(ss, BRUSH_BUILTIN_##idname, sd, br) +#define SCULPT_get_bool(ss, idname, sd, br) SCULPT_get_int(ss, idname, sd, br) + +int SCULPT_get_vector_intern( + const SculptSession *ss, const char *idname, float out[4], const Sculpt *sd, const Brush *br); +#define SCULPT_get_vector(ss, idname, out, sd, br) \ + SCULPT_get_vector_intern(ss, BRUSH_BUILTIN_##idname, out, sd, br) + +/** \} */ + +float SCULPT_calc_concavity(SculptSession *ss, PBVHVertRef vref); + +/* +If useAccurateSolver is false, a faster but less accurate +power solver will be used. If true then BLI_eigen_solve_selfadjoint_m3 +will be called. + +Must call BKE_sculpt_ensure_curvature_dir to ensure ss->attrs.curvature_dir exists. +*/ +bool SCULPT_calc_principle_curvatures(SculptSession *ss, + PBVHVertRef vertex, + SculptCurvatureData *out, + bool useAccurateSolver); + +void SCULPT_curvature_begin(SculptSession *ss, struct PBVHNode *node, bool useAccurateSolver); +void SCULPT_curvature_dir_get(SculptSession *ss, + PBVHVertRef v, + float dir[3], + bool useAccurateSolver); + +/* -------------------------------------------------------------------- */ +/** \name Cotangent API + * \{ */ + +void SCULPT_ensure_persistent_layers(SculptSession *ss, struct Object *ob); +void SCULPT_ensure_epmap(SculptSession *ss); +void SCULPT_ensure_vemap(SculptSession *ss); +bool SCULPT_dyntopo_automasking_init(const SculptSession *ss, + Sculpt *sd, + const Brush *br, + Object *ob, + ::blender::bke::dyntopo::DyntopoMaskCB *r_mask_cb, + void **r_mask_cb_data); +void SCULPT_dyntopo_automasking_end(void *mask_data); + +#define SCULPT_LAYER_PERS_CO "Persistent Base Co" +#define SCULPT_LAYER_PERS_NO "Persistent Base No" +#define SCULPT_LAYER_PERS_DISP "Persistent Base Height" +#define SCULPT_LAYER_DISP "__temp_layer_disp" + +// these tools don't support dynamic pbvh splitting during the stroke +#define DYNTOPO_HAS_DYNAMIC_SPLIT(tool) true + +#define SCULPT_stroke_needs_original(brush) \ + ELEM(brush->sculpt_tool, \ + SCULPT_TOOL_DRAW_SHARP, \ + SCULPT_TOOL_GRAB, \ + SCULPT_TOOL_ROTATE, \ + SCULPT_TOOL_THUMB, \ + SCULPT_TOOL_ELASTIC_DEFORM, \ + SCULPT_TOOL_BOUNDARY, \ + SCULPT_TOOL_POSE) + +// exponent to make boundary_smooth_factor more user-friendly +#define BOUNDARY_SMOOTH_EXP 2.0 + +bool SCULPT_needs_area_normal(SculptSession *ss, Sculpt *sd, Brush *brush); inline bool SCULPT_tool_is_paint(int tool) { return ELEM(tool, SCULPT_TOOL_PAINT, SCULPT_TOOL_SMEAR); @@ -2023,7 +2534,6 @@ void SCULPT_stroke_id_next(Object *ob); namespace blender::ed::sculpt_paint { void ensure_valid_pivot(const Object *ob, Scene *scene); -} /* -------------------------------------------------------------------- */ /** \name Topology island API @@ -2046,6 +2556,17 @@ void SCULPT_topology_islands_invalidate(SculptSession *ss); int SCULPT_vertex_island_get(const SculptSession *ss, PBVHVertRef vertex); /** \} */ +} // namespace blender::ed::sculpt_paint + +int SCULPT_get_symmetry_pass(const struct SculptSession *ss); +void SCULPT_update_object_bounding_box(Object *ob); + +#define SCULPT_boundary_flag_update BKE_sculpt_boundary_flag_update + +/* Some tools need original coordinates to be smoothed during + * autosmooth. + */ +#define SCULPT_tool_needs_smooth_origco(tool) ELEM(tool, SCULPT_TOOL_DRAW_SHARP) namespace blender::ed::sculpt_paint { float sculpt_calc_radius(ViewContext *vc, diff --git a/source/blender/editors/sculpt_paint/sculpt_multiplane_scrape.cc b/source/blender/editors/sculpt_paint/sculpt_multiplane_scrape.cc index cb198a77aa6..bfacea4390f 100644 --- a/source/blender/editors/sculpt_paint/sculpt_multiplane_scrape.cc +++ b/source/blender/editors/sculpt_paint/sculpt_multiplane_scrape.cc @@ -124,7 +124,6 @@ static void do_multiplane_scrape_brush_task(Object *ob, *ob, ss->cache->automasking.get(), *node); BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - if (!sculpt_brush_test_sq_fn(&test, vd.co)) { continue; } diff --git a/source/blender/editors/sculpt_paint/sculpt_ops.cc b/source/blender/editors/sculpt_paint/sculpt_ops.cc index 96348b334d1..abd96dc760e 100644 --- a/source/blender/editors/sculpt_paint/sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/sculpt_ops.cc @@ -9,9 +9,13 @@ #include "MEM_guardedalloc.h" +#include "BLI_alloca.h" #include "BLI_ghash.h" #include "BLI_math_matrix.hh" +#include "BLI_math_vector.h" #include "BLI_math_vector.hh" +#include "BLI_math_vector_types.hh" +#include "BLI_rand.h" #include "BLI_task.h" #include "BLI_utildefines.h" @@ -20,18 +24,23 @@ #include "DNA_brush_types.h" #include "DNA_customdata_types.h" #include "DNA_listBase.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" #include "DNA_node_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "BKE_attribute.hh" #include "BKE_brush.hh" +#include "BKE_bvhutils.hh" #include "BKE_ccg.h" #include "BKE_context.hh" #include "BKE_layer.hh" #include "BKE_main.hh" #include "BKE_mesh.hh" #include "BKE_mesh_mirror.hh" +#include "BKE_mesh_types.hh" +#include "BKE_modifier.hh" #include "BKE_multires.hh" #include "BKE_object.hh" #include "BKE_paint.hh" @@ -40,6 +49,7 @@ #include "BKE_scene.hh" #include "DEG_depsgraph.hh" +#include "DEG_depsgraph_query.hh" #include "IMB_colormanagement.hh" @@ -52,22 +62,39 @@ #include "ED_object.hh" #include "ED_screen.hh" #include "ED_sculpt.hh" +#include "ED_space_api.hh" +#include "ED_transform_snap_object_context.hh" +#include "ED_view3d.hh" #include "paint_intern.hh" #include "sculpt_intern.hh" #include "RNA_access.hh" #include "RNA_define.hh" +#include "RNA_prototypes.h" #include "UI_interface.hh" #include "UI_resources.hh" +#include "GPU_immediate.h" +#include "GPU_state.h" +#include "GPU_vertex_buffer.h" +#include "GPU_vertex_format.h" + #include "bmesh.hh" +#include "bmesh_log.hh" +#include "bmesh_tools.hh" + +#include "bmesh_idmap.hh" #include #include #include +using namespace blender::bke::paint; +using namespace blender; +using namespace blender::ed::sculpt_paint; + /* Reset the copy of the mesh that is being sculpted on (currently just for the layer brush). */ static int sculpt_set_persistent_base_exec(bContext *C, wmOperator * /*op*/) @@ -82,9 +109,7 @@ static int sculpt_set_persistent_base_exec(bContext *C, wmOperator * /*op*/) if (!BKE_base_is_visible(v3d, base)) { return OPERATOR_CANCELLED; } - - /* Do not allow in DynTopo just yet. */ - if (!ss || (ss && ss->bm)) { + if (!ss) { return OPERATOR_FINISHED; } SCULPT_vertex_random_access_ensure(ss); @@ -105,11 +130,9 @@ static int sculpt_set_persistent_base_exec(bContext *C, wmOperator * /*op*/) for (int i = 0; i < totvert; i++) { PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); - copy_v3_v3((float *)SCULPT_vertex_attr_get(vertex, ss->attrs.persistent_co), - SCULPT_vertex_co_get(ss, vertex)); - SCULPT_vertex_normal_get( - ss, vertex, (float *)SCULPT_vertex_attr_get(vertex, ss->attrs.persistent_no)); - (*(float *)SCULPT_vertex_attr_get(vertex, ss->attrs.persistent_disp)) = 0.0f; + vertex_attr_set(vertex, ss->attrs.persistent_co, SCULPT_vertex_co_get(ss, vertex)); + SCULPT_vertex_normal_get(ss, vertex, vertex_attr_ptr(vertex, ss->attrs.persistent_no)); + vertex_attr_set(vertex, ss->attrs.persistent_disp, 0.0f); } return OPERATOR_FINISHED; @@ -170,6 +193,15 @@ static bool sculpt_no_multires_poll(bContext *C) return false; } +static bool sculpt_only_bmesh_poll(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + if (SCULPT_mode_poll(C) && ob->sculpt && ob->sculpt->pbvh) { + return BKE_pbvh_type(ob->sculpt->pbvh) == PBVH_BMESH; + } + return false; +} + static int sculpt_symmetrize_exec(bContext *C, wmOperator *op) { using namespace blender::ed::sculpt_paint; @@ -200,7 +232,6 @@ static int sculpt_symmetrize_exec(bContext *C, wmOperator *op) * parts that symmetrize modifies). */ undo::push_begin(ob, op); undo::push_node(ob, nullptr, undo::Type::DyntopoSymmetrize); - BM_log_before_all_removed(ss->bm, ss->bm_log); BM_mesh_toolflags_set(ss->bm, true); @@ -211,15 +242,21 @@ static int sculpt_symmetrize_exec(bContext *C, wmOperator *op) sd->symmetrize_direction, dist, true); - dyntopo::triangulate(ss->bm); /* Bisect operator flags edges (keep tags clean for edge queue). */ BM_mesh_elem_hflag_disable_all(ss->bm, BM_EDGE, BM_ELEM_TAG, false); BM_mesh_toolflags_set(ss->bm, false); + BKE_pbvh_recalc_bmesh_boundary(ss->pbvh); + + /* De-duplicate element IDs. */ + BM_idmap_check_ids(ss->bm_idmap); + + BM_mesh_toolflags_set(ss->bm, false); + /* Finish undo. */ - BM_log_all_added(ss->bm, ss->bm_log); + BM_log_full_mesh(ss->bm, ss->bm_log); undo::push_end(ob); break; @@ -286,9 +323,13 @@ static void sculpt_init_session(Main *bmain, Depsgraph *depsgraph, Scene *scene, if (ob->sculpt != nullptr) { BKE_sculptsession_free(ob); } + ob->sculpt = MEM_new(__func__); ob->sculpt->mode_type = OB_MODE_SCULPT; + ob->sculpt->active_face.i = PBVH_REF_NONE; + ob->sculpt->active_vertex.i = PBVH_REF_NONE; + /* Trigger evaluation of modifier stack to ensure * multires modifier sets .runtime.ccg in * the evaluated mesh. @@ -300,8 +341,10 @@ static void sculpt_init_session(Main *bmain, Depsgraph *depsgraph, Scene *scene, /* This function expects a fully evaluated depsgraph. */ BKE_sculpt_update_object_for_edit(depsgraph, ob, false); - Mesh &mesh = *static_cast(ob->data); - if (mesh.attributes().contains(".sculpt_face_set")) { + BKE_sculptsession_update_attr_refs(ob); + + SculptSession *ss = ob->sculpt; + if (ss->face_sets || (ss->bm && ss->cd_faceset_offset != -1)) { /* Here we can detect geometry that was just added to Sculpt Mode as it has the * SCULPT_FACE_SET_NONE assigned, so we can create a new Face Set for it. */ /* In sculpt mode all geometry that is assigned to SCULPT_FACE_SET_NONE is considered as not @@ -312,8 +355,18 @@ static void sculpt_init_session(Main *bmain, Depsgraph *depsgraph, Scene *scene, /* TODO(pablodp606): Based on this we can improve the UX in future tools for creating new * objects, like moving the transform pivot position to the new area or masking existing * geometry. */ + + SCULPT_face_random_access_ensure(ss); const int new_face_set = face_set::find_next_available_id(*ob); - face_set::initialize_none_to_id(static_cast(ob->data), new_face_set); + + for (int i = 0; i < ss->totfaces; i++) { + PBVHFaceRef face = BKE_pbvh_index_to_face(ss->pbvh, i); + + int fset = SCULPT_face_set_get(ss, face); + if (fset == SCULPT_FACE_SET_NONE) { + SCULPT_face_set_set(ss, face, new_face_set); + } + } } } @@ -348,7 +401,8 @@ void ED_object_sculptmode_enter_ex(Main *bmain, Scene *scene, Object *ob, const bool force_dyntopo, - ReportList *reports) + ReportList *reports, + bool do_undo) { using namespace blender::ed::sculpt_paint; const int mode_flag = OB_MODE_SCULPT; @@ -373,32 +427,23 @@ void ED_object_sculptmode_enter_ex(Main *bmain, ED_paint_cursor_start(paint, SCULPT_mode_poll_view3d); + bool has_multires = false; + /* Check dynamic-topology flag; re-enter dynamic-topology mode when changing modes, * As long as no data was added that is not supported. */ if (mesh->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) { MultiresModifierData *mmd = BKE_sculpt_multires_active(scene, ob); const char *message_unsupported = nullptr; - if (mesh->corners_num != mesh->faces_num * 3) { - message_unsupported = RPT_("non-triangle face"); - } - else if (mmd != nullptr) { - message_unsupported = RPT_("multi-res modifier"); + if (mmd != nullptr) { + message_unsupported = TIP_("multi-res modifier"); + has_multires = true; } else { dyntopo::WarnFlag flag = dyntopo::check_attribute_warning(scene, ob); if (flag == 0) { /* pass */ } - else if (flag & dyntopo::VDATA) { - message_unsupported = RPT_("vertex data"); - } - else if (flag & dyntopo::EDATA) { - message_unsupported = RPT_("edge data"); - } - else if (flag & dyntopo::LDATA) { - message_unsupported = RPT_("face data"); - } else if (flag & dyntopo::MODIFIER) { message_unsupported = RPT_("constructive modifier"); } @@ -407,19 +452,45 @@ void ED_object_sculptmode_enter_ex(Main *bmain, } } - if ((message_unsupported == nullptr) || force_dyntopo) { + if (!has_multires && ((message_unsupported == nullptr) || force_dyntopo)) { /* Needed because we may be entering this mode before the undo system loads. */ wmWindowManager *wm = static_cast(bmain->wm.first); - bool has_undo = wm->undo_stack != nullptr; + bool has_undo = do_undo && wm->undo_stack != nullptr; + /* Undo push is needed to prevent memory leak. */ if (has_undo) { undo::push_begin_ex(ob, "Dynamic topology enable"); } + + bool need_bmlog = !ob->sculpt->bm_log; dyntopo::enable_ex(bmain, depsgraph, ob); if (has_undo) { undo::push_node(ob, nullptr, undo::Type::DyntopoBegin); undo::push_end(ob); } + else if (need_bmlog) { + if (ob->sculpt->bm_log) { + BM_log_free(ob->sculpt->bm_log); + ob->sculpt->bm_log = nullptr; + } + + if (ob->sculpt->bm_idmap) { + BM_idmap_destroy(ob->sculpt->bm_idmap); + ob->sculpt->bm_idmap = nullptr; + } + + /* Recreate idmap and log. */ + + BKE_sculpt_ensure_idmap(ob); + + /* See if we can rebuild the log from the undo stack. */ + undo::ensure_bmlog(ob); + + /* Create an empty log if reconstruction failed. */ + if (!ob->sculpt->bm_log) { + ob->sculpt->bm_log = BM_log_create(ob->sculpt->bm, ob->sculpt->bm_idmap); + } + } } else { BKE_reportf( @@ -441,7 +512,7 @@ void ED_object_sculptmode_enter(bContext *C, Depsgraph *depsgraph, ReportList *r ViewLayer *view_layer = CTX_data_view_layer(C); BKE_view_layer_synced_ensure(scene, view_layer); Object *ob = BKE_view_layer_active_object_get(view_layer); - ED_object_sculptmode_enter_ex(bmain, depsgraph, scene, ob, false, reports); + ED_object_sculptmode_enter_ex(bmain, depsgraph, scene, ob, false, reports, true); } void ED_object_sculptmode_exit_ex(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob) @@ -464,6 +535,11 @@ void ED_object_sculptmode_exit_ex(Main *bmain, Depsgraph *depsgraph, Scene *scen DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); } + /* Leave sculpt mode. We do this here to prevent + * the depsgraph spawning a PBVH_FACES after disabling + * dynamic topology below. */ + ob->mode &= ~mode_flag; + if (mesh->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) { /* Dynamic topology must be disabled before exiting sculpt * mode to ensure the undo stack stays in a consistent @@ -474,9 +550,6 @@ void ED_object_sculptmode_exit_ex(Main *bmain, Depsgraph *depsgraph, Scene *scen mesh->flag |= ME_SCULPT_DYNAMIC_TOPOLOGY; } - /* Leave sculpt mode. */ - ob->mode &= ~mode_flag; - BKE_sculptsession_free(ob); paint_cursor_delete_textures(); @@ -526,7 +599,7 @@ static int sculpt_mode_toggle_exec(bContext *C, wmOperator *op) if (depsgraph) { depsgraph = CTX_data_ensure_evaluated_depsgraph(C); } - ED_object_sculptmode_enter_ex(bmain, depsgraph, scene, ob, false, op->reports); + ED_object_sculptmode_enter_ex(bmain, depsgraph, scene, ob, false, op->reports, true); BKE_paint_toolslots_brush_validate(bmain, &ts->sculpt->paint); if (ob->mode & mode_flag) { @@ -616,6 +689,7 @@ void SCULPT_geometry_preview_lines_update(bContext *C, SculptSession *ss, float if (totpoints + (ni.size * 2) < max_preview_verts) { PBVHVertRef to_v = ni.vertex; int to_v_i = ni.index; + ss->preview_vert_list[totpoints] = from_v; totpoints++; ss->preview_vert_list[totpoints] = to_v; @@ -636,15 +710,151 @@ void SCULPT_geometry_preview_lines_update(bContext *C, SculptSession *ss, float ss->preview_vert_count = totpoints; } -static int sculpt_sample_color_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/) +#define SAMPLE_COLOR_PREVIEW_SIZE 60 +#define SAMPLE_COLOR_OFFSET_X -15 +#define SAMPLE_COLOR_OFFSET_Y -15 +typedef struct SampleColorCustomData { + void *draw_handle; + Object *active_object; + + float mval[2]; + + float initial_color[4]; + float sampled_color[4]; +} SampleColorCustomData; + +static void sculpt_sample_color_draw(const bContext * /* C */, ARegion * /* ar */, void *arg) { + SampleColorCustomData *sccd = (SampleColorCustomData *)arg; + GPU_line_width(2.0f); + GPU_line_smooth(true); + uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_AA); + + immUniform1f("size", 16.0f); + + const float origin_x = sccd->mval[0] + SAMPLE_COLOR_OFFSET_X; + const float origin_y = sccd->mval[1] + SAMPLE_COLOR_OFFSET_Y; + + immUniformColor3fvAlpha(sccd->sampled_color, 1.0f); + immRectf(pos, + origin_x, + origin_y, + origin_x - SAMPLE_COLOR_PREVIEW_SIZE, + origin_y - SAMPLE_COLOR_PREVIEW_SIZE); + + immUniformColor3fvAlpha(sccd->initial_color, 1.0f); + immRectf(pos, + origin_x - SAMPLE_COLOR_PREVIEW_SIZE, + origin_y, + origin_x - 2.0f * SAMPLE_COLOR_PREVIEW_SIZE, + origin_y - SAMPLE_COLOR_PREVIEW_SIZE); + + immUnbindProgram(); + GPU_line_smooth(false); +} + +static bool sculpt_sample_color_update_from_base(bContext *C, + const wmEvent *event, + SampleColorCustomData *sccd) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Base *base_sample = ED_view3d_give_base_under_cursor(C, event->mval); + if (base_sample == nullptr) { + return false; + } + + Object *object_sample = base_sample->object; + if (object_sample->type != OB_MESH) { + return false; + } + + Object *ob_eval = DEG_get_evaluated_object(depsgraph, object_sample); + Mesh *me_eval = BKE_object_get_evaluated_mesh(ob_eval); + MPropCol *vcol = static_cast( + CustomData_get_layer_for_write(&me_eval->vert_data, CD_PROP_COLOR, me_eval->verts_num)); + + if (!vcol) { + return false; + } + + ARegion *region = CTX_wm_region(C); + float global_loc[3]; + if (!ED_view3d_autodist_simple(region, event->mval, global_loc, 0, nullptr)) { + return false; + } + + float object_loc[3]; + mul_v3_m4v3(object_loc, ob_eval->world_to_object().ptr(), global_loc); + + BVHTreeFromMesh bvh; + BKE_bvhtree_from_mesh_get(&bvh, me_eval, BVHTREE_FROM_VERTS, 2); + BVHTreeNearest nearest; + nearest.index = -1; + nearest.dist_sq = FLT_MAX; + BLI_bvhtree_find_nearest(bvh.tree, object_loc, &nearest, bvh.nearest_callback, &bvh); + if (nearest.index == -1) { + return false; + } + free_bvhtree_from_mesh(&bvh); + + copy_v4_v4(sccd->sampled_color, vcol[nearest.index].color); + IMB_colormanagement_scene_linear_to_srgb_v3(sccd->sampled_color, sccd->sampled_color); + return true; +} + +static int sculpt_sample_color_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + ARegion *region = CTX_wm_region(C); + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + Scene *scene = CTX_data_scene(C); + Brush *brush = BKE_paint_brush(&sd->paint); + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + + SampleColorCustomData *sccd = (SampleColorCustomData *)op->customdata; + + /* Finish operation on release. */ + if (event->val == KM_RELEASE) { + float color_srgb[3]; + copy_v3_v3(color_srgb, sccd->sampled_color); + BKE_brush_color_set(scene, brush, sccd->sampled_color); + WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); + ED_region_draw_cb_exit(region->type, sccd->draw_handle); + ED_region_tag_redraw(region); + MEM_freeN(sccd); + ss->draw_faded_cursor = false; + return OPERATOR_FINISHED; + } + + SculptCursorGeometryInfo sgi; + sccd->mval[0] = event->mval[0]; + sccd->mval[1] = event->mval[1]; + + const bool over_mesh = SCULPT_cursor_geometry_info_update(C, &sgi, sccd->mval, false); + if (over_mesh) { + PBVHVertRef active_vertex = SCULPT_active_vertex_get(ss); + SCULPT_vertex_color_get(ss, active_vertex, sccd->sampled_color); + IMB_colormanagement_scene_linear_to_srgb_v3(sccd->sampled_color, sccd->sampled_color); + } + else { + sculpt_sample_color_update_from_base(C, event, sccd); + } + + ss->draw_faded_cursor = true; + ED_region_tag_redraw(region); + + return OPERATOR_RUNNING_MODAL; +} + +static int sculpt_sample_color_invoke(bContext *C, wmOperator *op, const wmEvent * /* event */) +{ + ARegion *region = CTX_wm_region(C); Sculpt *sd = CTX_data_tool_settings(C)->sculpt; Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); Brush *brush = BKE_paint_brush(&sd->paint); SculptSession *ss = ob->sculpt; - PBVHVertRef active_vertex = SCULPT_active_vertex_get(ss); - float active_vertex_color[4]; if (!SCULPT_handles_colors_report(ss, op->reports)) { return OPERATOR_CANCELLED; @@ -658,21 +868,33 @@ static int sculpt_sample_color_invoke(bContext *C, wmOperator *op, const wmEvent BKE_sculpt_update_object_for_edit(CTX_data_depsgraph_pointer(C), ob, false); - /* No color attribute? Set color to white. */ if (!SCULPT_has_colors(ss)) { - copy_v4_fl(active_vertex_color, 1.0f); - } - else { - SCULPT_vertex_color_get(ss, active_vertex, active_vertex_color); + BKE_report(op->reports, RPT_ERROR, "Mesh has no color attributes."); + return OPERATOR_CANCELLED; } + const PBVHVertRef active_vertex = SCULPT_active_vertex_get(ss); + float active_vertex_color[4]; + + SCULPT_vertex_color_get(ss, active_vertex, active_vertex_color); + float color_srgb[3]; IMB_colormanagement_scene_linear_to_srgb_v3(color_srgb, active_vertex_color); BKE_brush_color_set(scene, brush, color_srgb); - WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); + SampleColorCustomData *sccd = MEM_cnew("Sample Color Custom Data"); + copy_v4_v4(sccd->sampled_color, active_vertex_color); + copy_v4_v4(sccd->initial_color, BKE_brush_color_get(scene, brush)); - return OPERATOR_FINISHED; + sccd->draw_handle = ED_region_draw_cb_activate( + region->type, sculpt_sample_color_draw, sccd, REGION_DRAW_POST_PIXEL); + + op->customdata = sccd; + + WM_event_add_modal_handler(C, op); + ED_region_tag_redraw(region); + + return OPERATOR_RUNNING_MODAL; } static void SCULPT_OT_sample_color(wmOperatorType *ot) @@ -684,6 +906,7 @@ static void SCULPT_OT_sample_color(wmOperatorType *ot) /* api callbacks */ ot->invoke = sculpt_sample_color_invoke; + ot->modal = sculpt_sample_color_modal; ot->poll = SCULPT_mode_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_DEPENDS_ON_CURSOR; @@ -745,7 +968,7 @@ struct MaskByColorContiguousFloodFillData { float threshold; bool invert; float *new_mask; - float initial_color[3]; + float initial_color[4]; }; static void do_mask_by_color_contiguous_update_node(Object *ob, @@ -784,11 +1007,11 @@ static void do_mask_by_color_contiguous_update_node(Object *ob, static bool sculpt_mask_by_color_contiguous_floodfill( SculptSession *ss, PBVHVertRef from_v, PBVHVertRef to_v, bool is_duplicate, void *userdata) { - int from_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, from_v); - int to_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, to_v); - MaskByColorContiguousFloodFillData *data = static_cast( userdata); + int to_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, to_v); + int from_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, from_v); + float current_color[4]; SCULPT_vertex_color_get(ss, to_v, current_color); @@ -944,8 +1167,8 @@ static int sculpt_mask_by_color_invoke(bContext *C, wmOperator *op, const wmEven BKE_sculpt_update_object_for_edit(depsgraph, ob, false); SCULPT_vertex_random_access_ensure(ss); - /* Tools that are not brushes do not have the brush gizmo to update the vertex as the mouse move, - * so it needs to be updated here. */ + /* Tools that are not brushes do not have the brush gizmo to update the vertex as the mouse + * move, so it needs to be updated here. */ SculptCursorGeometryInfo sgi; const float mval_fl[2] = {float(event->mval[0]), float(event->mval[1])}; SCULPT_cursor_geometry_info_update(C, &sgi, mval_fl, false); @@ -962,6 +1185,8 @@ static int sculpt_mask_by_color_invoke(bContext *C, wmOperator *op, const wmEven BKE_pbvh_ensure_node_loops(ss->pbvh); } + BKE_sculpt_mask_layers_ensure(depsgraph, CTX_data_main(C), ob, ss->multires.modifier); + if (RNA_boolean_get(op->ptr, "contiguous")) { sculpt_mask_by_color_contiguous(ob, active_vertex, threshold, invert, preserve_mask); } @@ -1012,9 +1237,91 @@ static void SCULPT_OT_mask_by_color(wmOperatorType *ot) 0.0f, 1.0f); } - } // namespace blender::ed::sculpt_paint::mask +static int sculpt_reset_brushes_exec(bContext *C, wmOperator * /*op*/) +{ + Main *bmain = CTX_data_main(C); + + LISTBASE_FOREACH (Brush *, br, &bmain->brushes) { + if (br->ob_mode != OB_MODE_SCULPT) { + continue; + } + BKE_brush_sculpt_reset(br); + WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, br); + } + + return OPERATOR_FINISHED; +} + +static void SCULPT_OT_reset_brushes(struct wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Reset Sculpt Brushes"; + ot->idname = "SCULPT_OT_reset_brushes"; + ot->description = "Resets all sculpt brushes to their default value"; + + /* API callbacks. */ + ot->exec = sculpt_reset_brushes_exec; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER; +} + +static int sculpt_set_limit_surface_exec(bContext *C, wmOperator * /* op */) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Object *ob = CTX_data_active_object(C); + BKE_sculpt_update_object_for_edit(depsgraph, ob, false); + SculptSession *ss = ob->sculpt; + + if (!ss) { + return OPERATOR_FINISHED; + } + + SCULPT_vertex_random_access_ensure(ss); + + SculptAttributeParams params = {}; + + if (!ss->attrs.limit_surface) { + ss->attrs.limit_surface = BKE_sculpt_attribute_ensure( + ob, AttrDomain::Point, CD_PROP_FLOAT3, SCULPT_ATTRIBUTE_NAME(limit_surface), ¶ms); + } + + const SculptAttribute *scl = ss->attrs.limit_surface; + + const int totvert = SCULPT_vertex_count_get(ss); + const bool weighted = false; + for (int i = 0; i < totvert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + float *f = vertex_attr_ptr(vertex, scl); + + smooth::neighbor_coords_average(ss, f, vertex, 0.0, 0.0f, weighted); + } + + return OPERATOR_FINISHED; +} + +static void SCULPT_OT_set_limit_surface(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Set Limit Surface"; + ot->idname = "SCULPT_OT_set_limit_surface"; + ot->description = "Calculates and stores a limit surface from the current mesh"; + + /* API callbacks. */ + ot->exec = sculpt_set_limit_surface_exec; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +typedef struct BMLinkItem { + struct BMLinkItem *next, *prev; + BMVert *item; + int depth; +} BMLinkItem; + enum CavityBakeMixMode { AUTOMASK_BAKE_MIX, AUTOMASK_BAKE_MULTIPLY, @@ -1057,11 +1364,9 @@ static void sculpt_bake_cavity_exec_task(Object *ob, case AUTOMASK_BAKE_MULTIPLY: mask = vd.mask * automask; break; - break; case AUTOMASK_BAKE_DIVIDE: mask = automask > 0.00001f ? vd.mask / automask : 0.0f; break; - break; case AUTOMASK_BAKE_ADD: mask = vd.mask + automask; break; @@ -1295,12 +1600,109 @@ static void SCULPT_OT_mask_from_cavity(wmOperatorType *ot) RNA_def_boolean(ot->srna, "invert", false, "Cavity (Inverted)", ""); } +static int sculpt_reveal_all_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + + Mesh *mesh = BKE_object_get_original_mesh(ob); + + BKE_sculpt_update_object_for_edit(depsgraph, ob, false); + + if (!ss->pbvh) { + return OPERATOR_CANCELLED; + } + + bool with_bmesh = BKE_pbvh_type(ss->pbvh) == PBVH_BMESH; + + Vector nodes = blender::bke::pbvh::search_gather(ss->pbvh, {}); + + if (nodes.is_empty()) { + return OPERATOR_CANCELLED; + } + + /* Propagate face hide state to verts for undo. */ + hide::sync_all_from_faces(*ob); + + undo::push_begin(ob, op); + + for (PBVHNode *node : nodes) { + BKE_pbvh_node_mark_update_visibility(node); + + if (!with_bmesh) { + undo::push_node(ob, node, undo::Type::HideVert); + } + } + + SCULPT_topology_islands_invalidate(ss); + + if (!with_bmesh) { + /* As an optimization, free the hide attribute when making all geometry visible. This allows + * reduced memory usage without manually clearing it later, and allows sculpt operations to + * avoid checking element's hide status. */ + CustomData_free_layer_named(&mesh->face_data, ".hide_poly", mesh->faces_num); + ss->hide_poly = nullptr; + } + else { + undo::push_node(ob, nodes[0], undo::Type::HideVert); + + BMIter iter; + BMFace *f; + BMVert *v; + + BM_ITER_MESH (v, &iter, ss->bm, BM_VERTS_OF_MESH) { + BM_log_vert_if_modified(ss->bm, ss->bm_log, v); + } + BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { + BM_log_face_modified(ss->bm, ss->bm_log, f); + } + + face_set::visibility_all_set(ob, true); + } + + hide::sync_all_from_faces(*ob); + + /* NOTE: #SCULPT_visibility_sync_all_from_faces may have deleted + * `pbvh->hide_vert` if hide_poly did not exist, which is why + * we call #BKE_pbvh_update_hide_attributes_from_mesh here instead of + * after #CustomData_free_layer_named above. */ + if (!with_bmesh) { + BKE_pbvh_update_hide_attributes_from_mesh(ss->pbvh); + } + + bke::pbvh::update_visibility(*ss->pbvh); + + undo::push_end(ob); + + SCULPT_tag_update_overlays(C); + DEG_id_tag_update(&ob->id, ID_RECALC_SHADING); + ED_region_tag_redraw(CTX_wm_region(C)); + + return OPERATOR_FINISHED; +} + +static void SCULPT_OT_reveal_all(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Reveal All"; + ot->idname = "SCULPT_OT_reveal_all"; + ot->description = "Unhide all geometry"; + + /* Api callbacks. */ + ot->exec = sculpt_reveal_all_exec; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + void ED_operatortypes_sculpt() { using namespace blender::ed::sculpt_paint; WM_operatortype_append(SCULPT_OT_brush_stroke); WM_operatortype_append(SCULPT_OT_sculptmode_toggle); WM_operatortype_append(SCULPT_OT_set_persistent_base); + WM_operatortype_append(SCULPT_OT_set_limit_surface); WM_operatortype_append(dyntopo::SCULPT_OT_dynamic_topology_toggle); WM_operatortype_append(SCULPT_OT_optimize); WM_operatortype_append(SCULPT_OT_symmetrize); @@ -1327,6 +1729,7 @@ void ED_operatortypes_sculpt() WM_operatortype_append(dyntopo::SCULPT_OT_dyntopo_detail_size_edit); WM_operatortype_append(mask::SCULPT_OT_mask_init); + WM_operatortype_append(SCULPT_OT_reset_brushes); WM_operatortype_append(expand::SCULPT_OT_expand); WM_operatortype_append(SCULPT_OT_mask_from_cavity); } diff --git a/source/blender/editors/sculpt_paint/sculpt_paint_color.cc b/source/blender/editors/sculpt_paint/sculpt_paint_color.cc index 62315b7e6b0..5d505793722 100644 --- a/source/blender/editors/sculpt_paint/sculpt_paint_color.cc +++ b/source/blender/editors/sculpt_paint/sculpt_paint_color.cc @@ -16,6 +16,9 @@ #include "BLI_task.h" #include "BLI_vector.hh" +#include "DNA_meshdata_types.h" +#include "DNA_scene_types.h" + #include "BKE_brush.hh" #include "BKE_colorband.hh" #include "BKE_colortools.hh" @@ -33,8 +36,10 @@ #include #include -namespace blender::ed::sculpt_paint::color { +using namespace blender; +using namespace blender::bke::paint; +namespace blender::ed::sculpt_paint::color { static void do_color_smooth_task(Object *ob, const Brush *brush, PBVHNode *node) { SculptSession *ss = ob->sculpt; @@ -69,12 +74,13 @@ static void do_color_smooth_task(Object *ob, const Brush *brush, PBVHNode *node) &automask_data); float smooth_color[4]; - smooth::neighbor_color_average(ss, smooth_color, vd.vertex); - float col[4]; + float color[4]; - SCULPT_vertex_color_get(ss, vd.vertex, col); - blend_color_interpolate_float(col, col, smooth_color, fade); - SCULPT_vertex_color_set(ss, vd.vertex, col); + smooth::neighbor_color_average(ss, smooth_color, vd.vertex); + + SCULPT_vertex_color_get(ss, vd.vertex, color); + blend_color_interpolate_float(color, color, smooth_color, fade); + SCULPT_vertex_color_set(ss, vd.vertex, color); } BKE_pbvh_vertex_iter_end; } @@ -88,14 +94,15 @@ static void do_paint_brush_task(Object *ob, SculptSession *ss = ob->sculpt; const float bstrength = fabsf(ss->cache->bstrength); + const SculptAttribute *buffer_scl = ss->attrs.smear_previous; + + const bool do_accum = brush->flag & BRUSH_ACCUMULATE; + PBVHVertexIter vd; - PBVHColorBufferNode *color_buffer; SculptOrigVertData orig_data; SCULPT_orig_vert_data_init(&orig_data, ob, node, undo::Type::Color); - color_buffer = BKE_pbvh_node_color_buffer_get(node); - SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, brush->falloff_shape); @@ -109,6 +116,9 @@ static void do_paint_brush_task(Object *ob, IMB_colormanagement_srgb_to_scene_linear_v3(brush_color, brush_color); + /* get un-pressure-mapped alpha */ + float alpha = BKE_brush_alpha_get(ss->scene, brush); + auto_mask::NodeData automask_data = auto_mask::node_begin( *ob, ss->cache->automasking.get(), *node); @@ -131,13 +141,28 @@ static void do_paint_brush_task(Object *ob, } BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); + SCULPT_vertex_check_origdata(ss, vd.vertex); + + /* Check if we have a new stroke, in which we need to zero + * our temp layer. Do this here before the brush check + * to ensure any geomtry dyntopo might subdivide has + * valid state. + */ + float *color_buffer = vertex_attr_ptr(vd.vertex, + buffer_scl); // mv->origcolor; + if (blender::bke::sculpt::stroke_id_test(ss, vd.vertex, STROKEID_USER_PREV_COLOR)) { + zero_v4(color_buffer); + } bool affect_vertex = false; float distance_to_stroke_location = 0.0f; if (brush->tip_roundness < 1.0f) { - affect_vertex = SCULPT_brush_test_cube( - &test, vd.co, mat, brush->tip_roundness, brush->tip_scale_x); + affect_vertex = SCULPT_brush_test_cube(&test, + vd.co, + mat, + brush->tip_roundness, + brush->tip_scale_x, + brush->falloff_shape != PAINT_FALLOFF_SHAPE_TUBE); distance_to_stroke_location = ss->cache->radius * test.dist; } else { @@ -184,20 +209,32 @@ static void do_paint_brush_task(Object *ob, /* Interpolate with the wet_mix color for wet paint mixing. */ blend_color_interpolate_float( paint_color, paint_color, wet_mix_color, ss->cache->paint_brush.wet_mix); - blend_color_mix_float(color_buffer->color[vd.i], color_buffer->color[vd.i], paint_color); + blend_color_mix_float(color_buffer, color_buffer, paint_color); /* Final mix over the original color using brush alpha. We apply auto-making again * at this point to avoid washing out non-binary masking modes like cavity masking. */ float automasking = auto_mask::factor_get( ss->cache->automasking.get(), ss, vd.vertex, &automask_data); - const float alpha = BKE_brush_alpha_get(ss->scene, brush); - mul_v4_v4fl(buffer_color, color_buffer->color[vd.i], alpha * automasking); + mul_v4_v4fl(buffer_color, color_buffer, alpha * automasking); - float4 col; - SCULPT_vertex_color_get(ss, vd.vertex, col); - IMB_blend_color_float(col, orig_data.col, buffer_color, IMB_BlendMode(brush->blend)); - col = math::clamp(col, 0.0f, 1.0f); - SCULPT_vertex_color_set(ss, vd.vertex, col); + float4 vcolor; + SCULPT_vertex_color_get(ss, vd.vertex, vcolor); + + if (do_accum) { + mul_v4_fl(buffer_color, fade); + + IMB_blend_color_float(vcolor, vcolor, buffer_color, IMB_BlendMode(brush->blend)); + vcolor[3] = 1.0f; + } + else { + IMB_blend_color_float(vcolor, + vertex_attr_ptr(vd.vertex, ss->attrs.orig_color), + buffer_color, + IMB_BlendMode(brush->blend)); + } + + vcolor = math::clamp(vcolor, 0.0f, 1.0f); + SCULPT_vertex_color_set(ss, vd.vertex, vcolor); } BKE_pbvh_vertex_iter_end; } @@ -236,6 +273,7 @@ static void do_sample_wet_paint_task(SculptSession *ss, } void do_paint_brush(PaintModeSettings *paint_mode_settings, + Scene *scene, Sculpt *sd, Object *ob, Span nodes, @@ -246,13 +284,15 @@ void do_paint_brush(PaintModeSettings *paint_mode_settings, return; } - Brush *brush = BKE_paint_brush(&sd->paint); SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); if (!SCULPT_has_colors(ss)) { return; } + BKE_sculpt_ensure_origcolor(ob); + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { if (SCULPT_stroke_is_first_brush_step(ss->cache)) { ss->cache->density_seed = float(BLI_hash_int_01(ss->cache->location[0] * 1000)); @@ -274,6 +314,15 @@ void do_paint_brush(PaintModeSettings *paint_mode_settings, } } + float brush_color[4] = {0.0f, 0.0f, 0.0f, 1.0f}; + + if (ss->cache->invert) { + copy_v4_v4(brush_color, BKE_brush_secondary_color_get(scene, brush)); + } + else { + copy_v4_v4(brush_color, BKE_brush_color_get(scene, brush)); + } + /* Smooth colors mode. */ if (ss->cache->alt_smooth) { threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { @@ -323,6 +372,20 @@ void do_paint_brush(PaintModeSettings *paint_mode_settings, } } + SculptAttributeParams params = {}; + SculptAttributeParams params_id = {}; + params.stroke_only = true; + params_id.nointerp = params.stroke_only = true; + + BKE_sculpt_ensure_origcolor(ob); + + if (!ss->attrs.smear_previous) { + ss->attrs.smear_previous = BKE_sculpt_attribute_ensure( + ob, AttrDomain::Point, CD_PROP_COLOR, SCULPT_ATTRIBUTE_NAME(smear_previous), ¶ms); + } + + SCULPT_stroke_id_ensure(ob); + threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { for (const int i : range) { do_paint_brush_task(ob, brush, mat, wet_color, nodes[i]); @@ -335,6 +398,8 @@ static void do_smear_brush_task(Object *ob, const Brush *brush, PBVHNode *node) SculptSession *ss = ob->sculpt; const float bstrength = ss->cache->bstrength; + const float blend = brush->smear_deform_blend; + PBVHVertexIter vd; SculptBrushTest test; @@ -374,6 +439,10 @@ static void do_smear_brush_task(Object *ob, const Brush *brush, PBVHNode *node) float current_disp[3]; float current_disp_norm[3]; + float interp_color[4]; + float *prev_color = vertex_attr_ptr(vd.vertex, ss->attrs.smear_previous); + + copy_v4_v4(interp_color, prev_color); float no[3]; SCULPT_vertex_normal_get(ss, vd.vertex, no); @@ -449,7 +518,8 @@ static void do_smear_brush_task(Object *ob, const Brush *brush, PBVHNode *node) continue; } - const float *neighbor_color = ss->cache->prev_colors[ni.index]; + const float *neighbor_color = vertex_attr_ptr(ni.vertex, + ss->attrs.smear_previous); float color_interp = -dot_v3v3(current_disp_norm, vertex_disp_norm); /* Square directional weight to get a somewhat sharper result. */ @@ -468,41 +538,41 @@ static void do_smear_brush_task(Object *ob, const Brush *brush, PBVHNode *node) float col[4]; SCULPT_vertex_color_get(ss, vd.vertex, col); - blend_color_interpolate_float(col, ss->cache->prev_colors[vd.index], accum, fade); + + blend_color_interpolate_float(col, prev_color, interp_color, fade * blend); SCULPT_vertex_color_set(ss, vd.vertex, col); } BKE_pbvh_vertex_iter_end; } -static void do_smear_store_prev_colors_task(SculptSession *ss, - PBVHNode *node, - float (*prev_colors)[4]) +static void do_smear_store_prev_colors_task(SculptSession *ss, PBVHNode *node) { PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - SCULPT_vertex_color_get(ss, vd.vertex, prev_colors[vd.index]); + SCULPT_vertex_color_get( + ss, vd.vertex, vertex_attr_ptr(vd.vertex, ss->attrs.smear_previous)); } BKE_pbvh_vertex_iter_end; } void do_smear_brush(Sculpt *sd, Object *ob, Span nodes) { - Brush *brush = BKE_paint_brush(&sd->paint); SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); if (!SCULPT_has_colors(ss) || ss->cache->bstrength == 0.0f) { return; } - const int totvert = SCULPT_vertex_count_get(ss); + SCULPT_vertex_random_access_ensure(ss); - if (!ss->cache->prev_colors) { - ss->cache->prev_colors = MEM_cnew_array(totvert, __func__); - for (int i = 0; i < totvert; i++) { - PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + if (!ss->attrs.smear_previous) { + SculptAttributeParams params = {}; + params.stroke_only = true; - SCULPT_vertex_color_get(ss, vertex, ss->cache->prev_colors[i]); - } + ss->attrs.smear_previous = BKE_sculpt_attribute_ensure( + ob, AttrDomain::Point, CD_PROP_COLOR, SCULPT_ATTRIBUTE_NAME(smear_previous), ¶ms); } BKE_curvemapping_init(brush->curve); @@ -519,7 +589,7 @@ void do_smear_brush(Sculpt *sd, Object *ob, Span nodes) /* Smear mode. */ threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { for (const int i : range) { - do_smear_store_prev_colors_task(ss, nodes[i], ss->cache->prev_colors); + do_smear_store_prev_colors_task(ss, nodes[i]); } }); threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { diff --git a/source/blender/editors/sculpt_paint/sculpt_paint_image.cc b/source/blender/editors/sculpt_paint/sculpt_paint_image.cc index edf0b0818ee..8fe3aa15b91 100644 --- a/source/blender/editors/sculpt_paint/sculpt_paint_image.cc +++ b/source/blender/editors/sculpt_paint/sculpt_paint_image.cc @@ -11,6 +11,7 @@ #include "ED_paint.hh" +#include "BLI_string_ref.hh" #include "BLI_math_color_blend.h" #include "BLI_math_geom.h" #include "BLI_task.h" @@ -175,22 +176,24 @@ template class PaintingKernel { continue; } + PBVHVertRef fakevert = BKE_pbvh_make_vref(0LL); + float4 color = image_accessor.read_pixel(image_buffer); const float3 normal(0.0f, 0.0f, 0.0f); const float3 face_normal(0.0f, 0.0f, 0.0f); const float mask = 0.0f; - const float falloff_strength = SCULPT_brush_strength_factor( - ss, - brush, - pixel_pos, - sqrtf(test.dist), - normal, - face_normal, - mask, - BKE_pbvh_make_vref(PBVH_REF_NONE), - thread_id, - automask_data); + const float falloff_strength = SCULPT_brush_strength_factor(ss, + brush, + pixel_pos, + sqrtf(test.dist), + normal, + face_normal, + mask, + fakevert, + thread_id, + automask_data); + float4 paint_color = brush_color * falloff_strength * brush_strength; float4 buffer_color; @@ -244,7 +247,7 @@ template class PaintingKernel { } void init_brush_test() { - brush_test_fn = SCULPT_brush_test_init_with_falloff_shape(ss, &test, brush->falloff_shape); + brush_test_fn = SCULPT_brush_test_init(ss, &test); } /** diff --git a/source/blender/editors/sculpt_paint/sculpt_poly_loop.c b/source/blender/editors/sculpt_paint/sculpt_poly_loop.c new file mode 100644 index 00000000000..0a4ad224e48 --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_poly_loop.c @@ -0,0 +1,324 @@ +/* + * 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 edsculpt + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_hash.h" +#include "BLI_math.h" +#include "BLI_task.h" + +#include "DNA_brush_types.h" +#include "DNA_customdata_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BKE_brush.h" +#include "BKE_ccg.h" +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_customdata.h" +#include "BKE_mesh.h" +#include "BKE_mesh_fair.h" +#include "BKE_mesh_mapping.h" +#include "BKE_multires.h" +#include "BKE_node.h" +#include "BKE_object.hh" +#include "BKE_paint.h" +#include "BKE_pbvh_api.hh" +#include "BKE_scene.h" + +#include "DEG_depsgraph.h" + +#include "WM_api.h" +#include "WM_message.h" +#include "WM_toolsystem.h" +#include "WM_types.h" + +#include "ED_object.h" +#include "ED_screen.h" +#include "ED_sculpt.h" +#include "ED_view3d.h" +#include "paint_intern.h" +#include "sculpt_intern.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "bmesh.h" + +#include +#include + +static void sculpt_poly_loop_topology_data_ensure(Object *ob) +{ + SculptSession *ss = ob->sculpt; + Mesh *mesh = BKE_object_get_original_mesh(ob); + + if (!ss->epmap) { + BKE_mesh_edge_poly_map_create(&ss->epmap, + &ss->epmap_mem, + mesh->medge, + mesh->totedge, + mesh->mpoly, + mesh->totpoly, + mesh->mloop, + mesh->totloop); + } + if (!ss->vemap) { + const float(*vert_positions)[3] = BKE_mesh_vert_positions(mesh); + + BKE_mesh_vert_edge_map_create(&ss->vemap, + &ss->vemap_mem, + vert_positions, + mesh->medge, + mesh->totvert, + mesh->totedge, + false); + } +} + +#define SCULPT_FACE_SET_LOOP_STEP_NONE -1 +static bool sculpt_poly_loop_step(SculptSession *ss, + const int from_poly, + const int edge, + int *r_next_poly) +{ + if (!ss->epmap) { + return false; + } + + int next_poly = SCULPT_FACE_SET_LOOP_STEP_NONE; + for (int i = 0; i < ss->epmap[edge].count; i++) { + if (ss->epmap[edge].indices[i] != from_poly) { + next_poly = ss->epmap[edge].indices[i]; + } + } + + if (next_poly == SCULPT_FACE_SET_LOOP_STEP_NONE) { + return false; + } + + *r_next_poly = next_poly; + return true; +} + +static int sculpt_poly_loop_opposite_edge_in_quad(SculptSession *ss, + const int poly, + const int edge) +{ + if (ss->mpoly[poly].totloop != 4) { + return edge; + } + + int edge_index_in_poly = 0; + for (int i = 0; i < ss->mpoly[poly].totloop; i++) { + if (edge == ss->mloop[ss->mpoly[poly].loopstart + i].e) { + edge_index_in_poly = i; + break; + } + } + + const int next_edge_index_in_poly = (edge_index_in_poly + 2) % 4; + return ss->mloop[ss->mpoly[poly].loopstart + next_edge_index_in_poly].e; +} + +PBVHEdgeRef sculpt_poly_loop_initial_edge_from_cursor(Object *ob) +{ + SculptSession *ss = ob->sculpt; + Mesh *mesh = BKE_object_get_original_mesh(ob); + + sculpt_poly_loop_topology_data_ensure(ob); + + float *location = ss->cursor_location; + + float(*vert_positions)[3] = SCULPT_mesh_deformed_positions_get(ss); + MPoly *initial_poly = &mesh->mpoly[ss->active_face.i]; + + if (initial_poly->totloop != 4) { + return (PBVHEdgeRef){.i = PBVH_REF_NONE}; + } + + int closest_vert = mesh->mloop[initial_poly->loopstart].v; + for (int i = 0; i < initial_poly->totloop; i++) { + if (len_squared_v3v3(vert_positions[ss->mloop[initial_poly->loopstart + i].v], location) < + len_squared_v3v3(vert_positions[closest_vert], location)) + { + closest_vert = ss->mloop[initial_poly->loopstart + i].v; + } + } + + int initial_edge = ss->vemap[closest_vert].indices[0]; + int closest_vert_on_initial_edge = mesh->medge[initial_edge].v1 == closest_vert ? + mesh->medge[initial_edge].v2 : + mesh->medge[initial_edge].v1; + for (int i = 0; i < ss->vemap[closest_vert].count; i++) { + const int edge_index = ss->vemap[closest_vert].indices[i]; + const int other_vert = mesh->medge[edge_index].v1 == closest_vert ? + mesh->medge[edge_index].v2 : + mesh->medge[edge_index].v1; + if (dist_to_line_segment_v3( + location, vert_positions[closest_vert], vert_positions[other_vert]) < + dist_to_line_segment_v3( + location, vert_positions[closest_vert], vert_positions[closest_vert_on_initial_edge])) + { + initial_edge = edge_index; + closest_vert_on_initial_edge = other_vert; + } + } + return (PBVHEdgeRef){.i = initial_edge}; +} + +static void sculpt_poly_loop_iterate_and_fill(SculptSession *ss, + const int initial_poly, + const int initial_edge, + BLI_bitmap *poly_loop) +{ + int current_poly = initial_poly; + int current_edge = initial_edge; + int next_poly = SCULPT_FACE_SET_LOOP_STEP_NONE; + int max_steps = ss->totfaces; + + BLI_BITMAP_ENABLE(poly_loop, initial_poly); + + while (max_steps && sculpt_poly_loop_step(ss, current_poly, current_edge, &next_poly)) { + if (ss->face_sets[next_poly] == initial_poly) { + break; + } + if (ss->face_sets[next_poly] < 0) { + break; + } + if (ss->mpoly[next_poly].totloop != 4) { + break; + } + + BLI_BITMAP_ENABLE(poly_loop, next_poly); + current_edge = sculpt_poly_loop_opposite_edge_in_quad(ss, next_poly, current_edge); + current_poly = next_poly; + max_steps--; + } +} + +static void sculpt_poly_loop_symm_poly_find(Object *ob, + const int poly_index, + const int edge_index, + const char symm_it, + int *r_poly_index, + int *r_edge_index) +{ + if (symm_it == 0) { + *r_poly_index = poly_index; + *r_edge_index = edge_index; + return; + } + + SculptSession *ss = ob->sculpt; + Mesh *mesh = BKE_object_get_original_mesh(ob); + float(*vert_positions)[3] = SCULPT_mesh_deformed_positions_get(ss); + + MPoly *original_poly = &mesh->mpoly[poly_index]; + float original_poly_center[3]; + BKE_mesh_calc_poly_center( + original_poly, &mesh->mloop[original_poly->loopstart], vert_positions, original_poly_center); + + float symm_poly_center[3]; + flip_v3_v3(symm_poly_center, original_poly_center, symm_it); + + float min_poly_dist = FLT_MAX; + int search_poly_index = poly_index; + + for (int i = 0; i < mesh->totpoly; i++) { + MPoly *poly = &mesh->mpoly[i]; + float poly_center[3]; + BKE_mesh_calc_poly_center(poly, &mesh->mloop[poly->loopstart], vert_positions, poly_center); + const float dist_to_poly_squared = len_squared_v3v3(symm_poly_center, poly_center); + if (dist_to_poly_squared < min_poly_dist) { + min_poly_dist = dist_to_poly_squared; + search_poly_index = i; + } + } + + *r_poly_index = search_poly_index; + MPoly *search_poly = &mesh->mpoly[search_poly_index]; + + float original_edge_center[3]; + MEdge *original_edge = &mesh->medge[edge_index]; + mid_v3_v3v3( + original_edge_center, vert_positions[original_edge->v1], vert_positions[original_edge->v2]); + + float symm_edge_center[3]; + flip_v3_v3(symm_edge_center, original_edge_center, symm_it); + + float min_edge_dist = FLT_MAX; + int search_edge_index = edge_index; + + for (int i = 0; i < search_poly->totloop; i++) { + MLoop *loop = &mesh->mloop[search_poly->loopstart + i]; + MEdge *edge = &mesh->medge[loop->e]; + float edge_center[3]; + mid_v3_v3v3(edge_center, vert_positions[edge->v1], vert_positions[edge->v2]); + const float dist_to_edge_squared = len_squared_v3v3(symm_edge_center, edge_center); + if (dist_to_edge_squared < min_edge_dist) { + min_edge_dist = dist_to_edge_squared; + search_edge_index = loop->e; + } + + *r_edge_index = search_edge_index; + } +} + +BLI_bitmap *sculpt_poly_loop_from_cursor(Object *ob) +{ + SculptSession *ss = ob->sculpt; + Mesh *mesh = BKE_object_get_original_mesh(ob); + BLI_bitmap *poly_loop = BLI_BITMAP_NEW(mesh->totpoly, "poly loop"); + + sculpt_poly_loop_topology_data_ensure(ob); + const PBVHEdgeRef initial_edge = sculpt_poly_loop_initial_edge_from_cursor(ob); + const PBVHFaceRef initial_poly = ss->active_face; + + const int initial_edge_i = BKE_pbvh_edge_to_index(ss->pbvh, initial_edge); + const int initial_poly_i = BKE_pbvh_face_to_index(ss->pbvh, initial_poly); + + const char symm = SCULPT_mesh_symmetry_xyz_get(ob); + for (char symm_it = 0; symm_it <= symm; symm_it++) { + if (!SCULPT_is_symmetry_iteration_valid(symm_it, symm)) { + continue; + } + + int initial_poly_symm; + int initial_edge_symm; + sculpt_poly_loop_symm_poly_find( + ob, initial_poly_i, initial_edge_i, symm_it, &initial_poly_symm, &initial_edge_symm); + + const int initial_edge_opposite = sculpt_poly_loop_opposite_edge_in_quad( + ss, initial_poly_symm, initial_edge_symm); + + sculpt_poly_loop_iterate_and_fill(ss, initial_poly_symm, initial_edge_symm, poly_loop); + sculpt_poly_loop_iterate_and_fill(ss, initial_poly_symm, initial_edge_opposite, poly_loop); + } + + return poly_loop; +} diff --git a/source/blender/editors/sculpt_paint/sculpt_pose.cc b/source/blender/editors/sculpt_paint/sculpt_pose.cc index 3a4383f976e..b184f4d56e0 100644 --- a/source/blender/editors/sculpt_paint/sculpt_pose.cc +++ b/source/blender/editors/sculpt_paint/sculpt_pose.cc @@ -153,7 +153,7 @@ static void do_pose_brush_task(Object *ob, const Brush *brush, PBVHNode *node) *ob, ss->cache->automasking.get(), *node); BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); + SCULPT_orig_vert_data_update(&orig_data, vd.vertex); auto_mask::node_update(automask_data, vd); float total_disp[3]; @@ -521,8 +521,6 @@ void calc_pose_data(Object *ob, float *r_pose_origin, float *r_pose_factor) { - SCULPT_vertex_random_access_ensure(ss); - /* Calculate the pose rotation point based on the boundaries of the brush factor. */ SculptFloodFill flood; flood_fill::init_fill(ss, &flood); @@ -634,6 +632,7 @@ static int pose_brush_num_effective_segments(const Brush *brush) static SculptPoseIKChain *pose_ik_chain_init_topology( Object *ob, SculptSession *ss, Brush *br, const float initial_location[3], const float radius) { + SCULPT_vertex_random_access_ensure(ss); const float chain_segment_len = radius * (1.0f + br->pose_offset); float next_chain_segment_target[3]; @@ -752,6 +751,13 @@ static SculptPoseIKChain *pose_ik_chain_init_face_sets(Object *ob, copy_v3_v3(fdata.pose_initial_co, SCULPT_vertex_co_get(ss, current_vertex)); flood_fill::execute(ss, &flood, pose_face_sets_floodfill_cb, &fdata); + if (!fdata.next_face_set_found) { + for (int i = s; i < ik_chain->tot_segments; i++) { + zero_v3(ik_chain->segments[i].orig); + } + break; + } + if (fdata.tot_co > 0) { mul_v3_fl(fdata.pose_origin, 1.0f / float(fdata.tot_co)); copy_v3_v3(ik_chain->segments[s].orig, fdata.pose_origin); @@ -921,6 +927,8 @@ SculptPoseIKChain *ik_chain_init( const bool use_fake_neighbors = !(br->flag2 & BRUSH_USE_CONNECTED_ONLY); + SCULPT_boundary_info_ensure(ob); + if (use_fake_neighbors) { SCULPT_fake_neighbors_ensure(ob, br->disconnected_distance_max); SCULPT_fake_neighbors_enable(ob); diff --git a/source/blender/editors/sculpt_paint/sculpt_replay.c b/source/blender/editors/sculpt_paint/sculpt_replay.c new file mode 100644 index 00000000000..bcaa674fa8a --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_replay.c @@ -0,0 +1,1233 @@ +#include "MEM_guardedalloc.h" + +#include "DNA_brush_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_texture_types.h" + +#include "BKE_context.h" +#include "BKE_main.h" +#include "BKE_object.hh" +#include "BKE_paint.h" +#include "BKE_pbvh_api.hh" +#include "BKE_scene.h" +#include "BKE_screen.h" + +#include "DEG_depsgraph.h" +#include "ED_view3d.h" + +#include "BLI_array.h" +#include "BLI_buffer.h" +#include "BLI_compiler_attrs.h" +#include "BLI_compiler_compat.h" +#include "BLI_dynstr.h" +#include "BLI_ghash.h" +#include "BLI_math.h" +#include "BLI_memarena.h" +#include "BLI_mempool.h" +#include "BLI_rand.h" +#include "BLI_smallhash.h" +#include "BLI_string.h" +#include "BLI_utildefines.h" + +#include "PIL_time.h" + +#include "BKE_context.h" +#include "BKE_global.hh" +#include "BKE_paint.hh" +#include "BKE_pbvh_api.hh" + +#include "paint_intern.h" +#include "sculpt_intern.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "bmesh.h" +#include + +typedef struct SculptBrushSample { + Sculpt sd; // copy of sd settings + + float active_vertex_co[3]; + float active_face_co[3]; + + bool have_active_vertex; + bool have_active_face; + + StrokeCache cache; + UnifiedPaintSettings ups; + PaintStroke stroke; + + double time; +} SculptBrushSample; + +typedef struct SculptReplayLog { + SculptBrushSample *samples; + int totsample, samples_size; + Tex **textures; + int tot_textures, textures_size; + MemArena *arena; + SmallHash texmap; + + bool is_playing; +} SculptReplayLog; + +static SculptReplayLog *current_log = NULL; + +void SCULPT_replay_log_free(SculptReplayLog *log) +{ + MEM_SAFE_FREE(log->samples); + MEM_SAFE_FREE(log->textures); + + BLI_smallhash_release(&log->texmap); + BLI_memarena_free(log->arena); + MEM_freeN(log); +} + +SculptReplayLog *SCULPT_replay_log_create() +{ + SculptReplayLog *log = MEM_callocN(sizeof(*log), "SculptReplayLog"); + + log->arena = BLI_memarena_new(1024, __func__); + BLI_smallhash_init(&log->texmap); + + return log; +} + +void SCULPT_replay_log_end() +{ + if (!current_log) { + printf("could not find log!"); + return; + } + + SCULPT_replay_log_free(current_log); + current_log = NULL; +} +void SCULPT_replay_log_start() +{ + if (current_log) { + printf("%s: recording has already started. . .\n", __func__); + return; + } + + current_log = MEM_callocN(sizeof(*current_log), "sculpt replay log"); + current_log->arena = BLI_memarena_new(8192, "sculpt replay log"); +} + +#if 0 +# define WRITE(key, fmt, ...) \ + { \ + char _buf[256], _prefix[64]; \ + if (shead >= 0) { \ + sprintf(_prefix, "%s%s%s", stack[shead].prefix, stack[shead].op, key); \ + } \ + else { \ + sprintf(_prefix, "%s", key); \ + } \ + sprintf(_buf, "%s " fmt "\n", _prefix, __VA_ARGS__); \ + BLI_dynstr_append(out, _buf); \ + } \ + ((void *)0) + +# define STACK_PUSH(key, memberop) \ + shead++; \ + sprintf(stack[shead].prefix, "%s", key); \ + stack[shead].op = memberop + +# define STACK_POP() shead-- +#endif + +enum { + REPLAY_FLOAT, + REPLAY_INT, + REPLAY_VEC2, + REPLAY_VEC3, + REPLAY_VEC4, + REPLAY_STRUCT, + REPLAY_STRUCT_PTR, + REPLAY_BOOL, + REPLAY_BYTE, + REPLAY_SHORT, +}; + +struct ReplaySerialStruct; +typedef struct ReplaySerialDef { + char name[32]; + int type; //-1 is used for sentinal ending member list + int struct_offset; + struct ReplaySerialStruct *sdef; +} ReplaySerialDef; + +typedef struct ReplaySerialStruct { + char name[32]; + ReplaySerialDef *members; +} ReplaySerialStruct; + +#ifdef DEF +# undef DEF +#endif + +/* clang-format off */ +#define DEF(key, type, structtype, ...) {#key, type, offsetof(structtype, key), __VA_ARGS__} + +static ReplaySerialDef dyntopo_def[] = { + {"detail_range", REPLAY_FLOAT, offsetof(DynTopoSettings, detail_range)}, + {"detail_percent", REPLAY_FLOAT, offsetof(DynTopoSettings, detail_percent)}, + {"detail_size", REPLAY_FLOAT, offsetof(DynTopoSettings, detail_size)}, + {"constant_detail", REPLAY_FLOAT, offsetof(DynTopoSettings, constant_detail)}, + {"flag", REPLAY_SHORT, offsetof(DynTopoSettings, flag)}, + {"mode", REPLAY_SHORT, offsetof(DynTopoSettings, mode)}, + {"inherit", REPLAY_INT, offsetof(DynTopoSettings, inherit)}, + {"spacing", REPLAY_INT, offsetof(DynTopoSettings, spacing)}, + {"", -1, -1}}; +static ReplaySerialStruct DynTopoSettingsDef = {"DynTopoSettings", dyntopo_def}; + +static ReplaySerialDef paint_stroke_def[] = { + DEF(last_mouse_position, REPLAY_VEC2, PaintStroke), + DEF(last_world_space_position, REPLAY_VEC3, PaintStroke), + DEF(stroke_over_mesh, REPLAY_BOOL, PaintStroke), + DEF(stroke_distance, REPLAY_FLOAT, PaintStroke), + DEF(stroke_distance_t, REPLAY_FLOAT, PaintStroke), + DEF(stroke_started, REPLAY_BOOL, PaintStroke), + DEF(rake_started, REPLAY_BOOL, PaintStroke), + DEF(event_type, REPLAY_INT, PaintStroke), + DEF(stroke_init, REPLAY_BOOL, PaintStroke), + DEF(brush_init, REPLAY_BOOL, PaintStroke), + DEF(initial_mouse, REPLAY_VEC2, PaintStroke), + DEF(cached_size_pressure, REPLAY_FLOAT, PaintStroke), + DEF(last_pressure, REPLAY_FLOAT, PaintStroke), + DEF(stroke_mode, REPLAY_INT, PaintStroke), + DEF(last_tablet_event_pressure, REPLAY_FLOAT, PaintStroke), + DEF(pen_flip, REPLAY_INT, PaintStroke), + DEF(x_tilt, REPLAY_FLOAT, PaintStroke), + DEF(y_tilt, REPLAY_FLOAT, PaintStroke), + DEF(spacing, REPLAY_FLOAT, PaintStroke), + DEF(constrain_line, REPLAY_BOOL, PaintStroke), + DEF(constrained_pos, REPLAY_VEC2, PaintStroke), + {"", -1, -1} +}; + +static ReplaySerialStruct PaintStrokeDef = {"PaintStroke", paint_stroke_def}; + +static ReplaySerialDef brush_def[] = { + DEF(weight, REPLAY_FLOAT, Brush), + DEF(size, REPLAY_INT, Brush), + DEF(dyntopo, REPLAY_STRUCT, Brush, &DynTopoSettingsDef), + DEF(flag, REPLAY_INT, Brush), + DEF(flag2, REPLAY_INT, Brush), + DEF(automasking_flags, REPLAY_INT, Brush), + DEF(normal_radius_factor, REPLAY_FLOAT, Brush), + DEF(area_radius_factor, REPLAY_FLOAT, Brush), + DEF(wet_paint_radius_factor, REPLAY_FLOAT, Brush), + DEF(plane_trim, REPLAY_FLOAT, Brush), + DEF(height, REPLAY_FLOAT, Brush), + DEF(vcol_boundary_factor, REPLAY_FLOAT, Brush), + DEF(vcol_boundary_exponent, REPLAY_FLOAT, Brush), + DEF(topology_rake_factor, REPLAY_FLOAT, Brush), + DEF(topology_rake_radius_factor, REPLAY_FLOAT, Brush), + DEF(topology_rake_projection, REPLAY_FLOAT, Brush), + DEF(topology_rake_spacing, REPLAY_FLOAT, Brush), + DEF(tilt_strength_factor, REPLAY_FLOAT, Brush), + DEF(autosmooth_factor, REPLAY_FLOAT, Brush), + DEF(tilt_strength_factor, REPLAY_FLOAT, Brush), + DEF(autosmooth_radius_factor, REPLAY_FLOAT, Brush), + DEF(autosmooth_projection, REPLAY_FLOAT, Brush), + DEF(autosmooth_spacing, REPLAY_FLOAT, Brush), + DEF(boundary_smooth_factor, REPLAY_FLOAT, Brush), + DEF(hard_corner_pin, REPLAY_FLOAT, Brush), + DEF(sculpt_tool, REPLAY_BYTE, Brush), + DEF(falloff_shape, REPLAY_BYTE, Brush), + DEF(falloff_angle, REPLAY_FLOAT, Brush), + DEF(paint_flags, REPLAY_INT, Brush), + DEF(density, REPLAY_FLOAT, Brush), + DEF(wet_persistence, REPLAY_FLOAT, Brush), + DEF(wet_mix, REPLAY_FLOAT, Brush), + DEF(flow, REPLAY_FLOAT, Brush), + DEF(hardness, REPLAY_FLOAT, Brush), + DEF(alpha, REPLAY_FLOAT, Brush), + DEF(rgb, REPLAY_VEC3, Brush), + DEF(rate, REPLAY_FLOAT, Brush), + DEF(smooth_stroke_factor, REPLAY_FLOAT, Brush), + DEF(smooth_stroke_radius, REPLAY_INT, Brush), + DEF(spacing, REPLAY_INT, Brush), + DEF(overlay_flags, REPLAY_INT, Brush), + DEF(mask_pressure, REPLAY_INT, Brush), + DEF(jitter, REPLAY_FLOAT, Brush), + DEF(overlay_flags, REPLAY_INT, Brush), + DEF(sampling_flag, REPLAY_INT, Brush), + DEF(normal_weight, REPLAY_FLOAT, Brush), + DEF(blend, REPLAY_SHORT, Brush), + DEF(concave_mask_factor, REPLAY_FLOAT, Brush), + {"", -1, -1}}; + +static ReplaySerialStruct BrushDef = {"Brush", brush_def}; + +static ReplaySerialDef stroke_cache_def[] = { + DEF(bstrength, REPLAY_FLOAT, StrokeCache), + DEF(radius, REPLAY_FLOAT, StrokeCache), + DEF(pressure, REPLAY_FLOAT, StrokeCache), + DEF(brush, REPLAY_STRUCT_PTR, StrokeCache, &BrushDef), + DEF(location, REPLAY_VEC3, StrokeCache), + DEF(view_normal, REPLAY_VEC3, StrokeCache), + DEF(true_location, REPLAY_VEC3, StrokeCache), + DEF(location, REPLAY_VEC3, StrokeCache), + DEF(initial_radius, REPLAY_FLOAT, StrokeCache), + DEF(dyntopo_pixel_radius, REPLAY_FLOAT, StrokeCache), + DEF(radius_squared, REPLAY_FLOAT, StrokeCache), + DEF(iteration_count, REPLAY_INT, StrokeCache), + DEF(special_rotation, REPLAY_FLOAT, StrokeCache), + DEF(grab_delta, REPLAY_VEC3, StrokeCache), + DEF(grab_delta_symmetry, REPLAY_VEC3, StrokeCache), + DEF(old_grab_location, REPLAY_VEC3, StrokeCache), + DEF(orig_grab_location, REPLAY_VEC3, StrokeCache), + DEF(rake_rotation, REPLAY_VEC4, StrokeCache), + DEF(rake_rotation_symmetry, REPLAY_VEC4, StrokeCache), + DEF(is_rake_rotation_valid, REPLAY_BOOL, StrokeCache), + DEF(paint_face_set, REPLAY_INT, StrokeCache), + DEF(symmetry, REPLAY_INT, StrokeCache), + DEF(boundary_symmetry, REPLAY_INT, StrokeCache), + DEF(mirror_symmetry_pass, REPLAY_INT, StrokeCache), + DEF(true_view_normal, REPLAY_VEC3, StrokeCache), + DEF(view_normal, REPLAY_VEC3, StrokeCache), + DEF(sculpt_normal, REPLAY_VEC3, StrokeCache), + DEF(sculpt_normal_symm, REPLAY_VEC3, StrokeCache), + DEF(plane_offset, REPLAY_VEC3, StrokeCache), + DEF(radial_symmetry_pass, REPLAY_INT, StrokeCache), + DEF(last_center, REPLAY_VEC3, StrokeCache), + DEF(original, REPLAY_BOOL, StrokeCache), + DEF(initial_location, REPLAY_VEC3, StrokeCache), + DEF(true_initial_location, REPLAY_VEC3, StrokeCache), + DEF(initial_normal, REPLAY_VEC3, StrokeCache), + DEF(true_initial_normal, REPLAY_VEC3, StrokeCache), + DEF(vertex_rotation, REPLAY_FLOAT, StrokeCache), + DEF(plane_trim_squared, REPLAY_FLOAT, StrokeCache), + DEF(saved_smooth_size, REPLAY_FLOAT, StrokeCache), + DEF(alt_smooth, REPLAY_BOOL, StrokeCache), + DEF(density_seed, REPLAY_FLOAT, StrokeCache), + DEF(stroke_distance, REPLAY_FLOAT, StrokeCache), + DEF(stroke_distance_t, REPLAY_FLOAT, StrokeCache), + DEF(last_dyntopo_t, REPLAY_FLOAT, StrokeCache), + DEF(scale, REPLAY_VEC3, StrokeCache), + {"", -1, -1} +}; + +static ReplaySerialStruct StrokeCacheDef = {"StrokeCache", stroke_cache_def}; + +static ReplaySerialDef paint_def[] = { + DEF(symmetry_flags, REPLAY_INT, Paint), + {"", -1, -1} +}; +static ReplaySerialStruct PaintDef = {"Paint", paint_def}; + +static ReplaySerialDef sculpt_def[] = { + DEF(paint, REPLAY_STRUCT, Sculpt, &PaintDef), + DEF(detail_size, REPLAY_FLOAT, Sculpt), + DEF(detail_range , REPLAY_FLOAT, Sculpt), + DEF(constant_detail , REPLAY_FLOAT, Sculpt), + DEF(detail_percent , REPLAY_FLOAT, Sculpt), + DEF(dyntopo_spacing , REPLAY_INT, Sculpt), + DEF(automasking_flags, REPLAY_INT, Sculpt), + DEF(flags, REPLAY_INT, Sculpt), + {"", -1, -1} +}; + +static ReplaySerialStruct SculptDef = {"Sculpt", sculpt_def}; + +static ReplaySerialDef ups_def[] = { + DEF(size, REPLAY_INT, UnifiedPaintSettings), + DEF(unprojected_radius, REPLAY_FLOAT, UnifiedPaintSettings), + DEF(alpha, REPLAY_FLOAT, UnifiedPaintSettings), + DEF(weight, REPLAY_FLOAT, UnifiedPaintSettings), + DEF(rgb, REPLAY_VEC3, UnifiedPaintSettings), + DEF(secondary_rgb, REPLAY_VEC3, UnifiedPaintSettings), + DEF(flag, REPLAY_INT, UnifiedPaintSettings), + DEF(last_rake, REPLAY_VEC2, UnifiedPaintSettings), + DEF(last_rake_angle, REPLAY_FLOAT, UnifiedPaintSettings), + DEF(last_stroke_valid, REPLAY_INT, UnifiedPaintSettings), + DEF(average_stroke_accum, REPLAY_VEC3, UnifiedPaintSettings), + DEF(unprojected_radius, REPLAY_FLOAT, UnifiedPaintSettings), + DEF(average_stroke_counter, REPLAY_FLOAT, UnifiedPaintSettings), + DEF(brush_rotation, REPLAY_FLOAT, UnifiedPaintSettings), + DEF(brush_rotation_sec, REPLAY_FLOAT, UnifiedPaintSettings), + DEF(anchored_size, REPLAY_INT, UnifiedPaintSettings), + DEF(overlap_factor, REPLAY_FLOAT, UnifiedPaintSettings), + DEF(draw_inverted, REPLAY_BYTE, UnifiedPaintSettings), + DEF(stroke_active, REPLAY_BYTE, UnifiedPaintSettings), + DEF(draw_anchored, REPLAY_BYTE, UnifiedPaintSettings), + DEF(last_location, REPLAY_VEC3, UnifiedPaintSettings), + DEF(last_hit, REPLAY_INT, UnifiedPaintSettings), + DEF(anchored_initial_mouse, REPLAY_VEC2, UnifiedPaintSettings), + DEF(pixel_radius, REPLAY_FLOAT, UnifiedPaintSettings), + DEF(initial_pixel_radius, REPLAY_FLOAT, UnifiedPaintSettings), + DEF(size_pressure_value, REPLAY_FLOAT, UnifiedPaintSettings), + DEF(tex_mouse, REPLAY_VEC2, UnifiedPaintSettings), + DEF(mask_tex_mouse, REPLAY_VEC2, UnifiedPaintSettings), + {"", -1, -1} +}; +static ReplaySerialStruct UnifiedPaintSettingsDef = { + "UnifiedPaintSettings", ups_def +}; + +static ReplaySerialDef sample_def[] = { + {"active_vertex_co", REPLAY_VEC3, offsetof(SculptBrushSample, active_vertex_co)}, + {"active_face_co", REPLAY_VEC3, offsetof(SculptBrushSample, active_face_co)}, + {"have_active_vertex", REPLAY_BOOL, offsetof(SculptBrushSample, have_active_vertex)}, + {"have_active_face", REPLAY_BOOL, offsetof(SculptBrushSample, have_active_face)}, + {"cache", REPLAY_STRUCT, offsetof(SculptBrushSample, cache), &StrokeCacheDef}, + // {"brush", REPLAY_STRUCT, offsetof(SculptBrushSample, brush), &BrushDef}, + {"sd", REPLAY_STRUCT, offsetof(SculptBrushSample, sd), &SculptDef}, + DEF(ups, REPLAY_STRUCT, SculptBrushSample, &UnifiedPaintSettingsDef), + DEF(stroke, REPLAY_STRUCT, SculptBrushSample, &PaintStrokeDef), + {"", -1, -1}}; + +static ReplaySerialStruct SculptBrushSampleDef = {"SculptBrushSample", sample_def}; + +/* clang-format on */ + +typedef struct ReplaySerializer { + struct { + char prefix[256], op[32]; + } stack[16]; + int stack_head; + DynStr *out; +} ReplaySerializer; + +static void replay_samples_ensure_size(SculptReplayLog *log); + +void replay_write_path(ReplaySerializer *state, char *key) +{ + char buf[512]; + + if (state->stack_head >= 0) { + sprintf(buf, + "%s%s%s", + state->stack[state->stack_head].prefix, + state->stack[state->stack_head].op, + key); + } + else { + sprintf(buf, "%s", key); + } + + BLI_dynstr_append(state->out, buf); +} + +void replay_push_stack(ReplaySerializer *state, char *key, char *op) +{ + state->stack_head++; + + if (state->stack_head > 0) { + sprintf(state->stack[state->stack_head].prefix, + "%s%s%s", + state->stack[state->stack_head - 1].prefix, + state->stack[state->stack_head - 1].op, + key); + } + else { + sprintf(state->stack[state->stack_head].prefix, "%s", key); + } + + sprintf(state->stack[state->stack_head].op, "%s", op); +} + +void replay_pop_stack(ReplaySerializer *state) +{ + state->stack_head--; +} + +#define SKIP_WS \ + while (i < len && ELEM(buf[i], ' ', '\t', '\r')) \ + i++ + +#define SKIP_ALL_WS \ + while (i < len && ELEM(buf[i], ' ', '\t', '\r', '\n')) \ + i++ + +#include + +static int parse_replay_member(const char *buf, int len, ReplaySerialStruct *st, void *data) +{ + char *ptr = (char *)data; + int i = 0; + int n = 0; + + SKIP_WS; + ReplaySerialDef *mdef = NULL; + + while (buf[i] != ':') { + int a = strcspn(buf + i, ".-:"); + + if (a < 0 || i + a >= len) { + break; + } + + char *name = alloca(a + 1); + memcpy(name, buf + i, a); + name[a] = 0; + + i += a; + + while (ELEM(buf[i], '-', '>', '.')) { + i++; + } + + SKIP_WS; + + ReplaySerialDef *mdef2 = st->members; + while (mdef2->type != -1) { + if (STREQ(mdef2->name, name)) { + break; + } + mdef2++; + } + + if (mdef2->type == -1) { + printf("Failed to find memer \"%s!\n", name); + return len; + } + + SKIP_WS; + + ptr += mdef2->struct_offset; + + if (mdef2->type == REPLAY_STRUCT_PTR) { + void **vptr = (void **)ptr; + + if (!*vptr) { + char *line = alloca(len + 1); + memcpy(line, buf, len); + line[len] = 0; + + printf("error; missing memory for %s\n", line); + return len; + } + + ptr = (char *)*vptr; + st = mdef2->sdef; + } + else if (mdef2->type == REPLAY_STRUCT) { + st = mdef2->sdef; + } + + mdef = mdef2; + } + + if (!mdef) { + printf("replay parse error\n"); + return len; + } + + i++; + SKIP_WS; + + switch (mdef->type) { + case REPLAY_FLOAT: { + float f = 0.0; + + sscanf(buf + i, "%f%n", &f, &n); + i += n; + *(float *)ptr = f; + break; + } + case REPLAY_INT: { + int f = 0; + + sscanf(buf + i, "%d%n", &f, &n); + i += n; + *(int *)ptr = f; + break; + } + case REPLAY_BOOL: + case REPLAY_BYTE: { + int f = 0; + + sscanf(buf + i, "%d%n", &f, &n); + i += n; + *(unsigned char *)ptr = (unsigned char)f; + break; + } + case REPLAY_VEC2: { + float f[2]; + + sscanf(buf + i, "[%f,%f]%n", &f[0], &f[1], &n); + i += n; + + copy_v2_v2((float *)ptr, f); + break; + } + case REPLAY_VEC3: { + float f[3]; + + sscanf(buf + i, "[%f,%f,%f]%n", &f[0], &f[1], &f[2], &n); + i += n; + + copy_v3_v3((float *)ptr, f); + break; + } + case REPLAY_VEC4: { + float f[4]; + + sscanf(buf + i, "[%f,%f,%f,%f]%n", &f[0], &f[1], &f[2], &f[3], &n); + i += n; + + copy_v4_v4((float *)ptr, f); + break; + } + case REPLAY_SHORT: { + int f = 0; + + sscanf(buf + i, "%d%n", &f, &n); + i += n; + *(short *)ptr = (short)f; + break; + } + default: + printf("replay parse error: invalid type %d\n", mdef->type); + break; + } + return i; +} + +// data1 is dest, data2 is source +static void replay_load(ReplaySerialStruct *st, void *data1, void *data2) +{ + ReplaySerialDef *mdef = st->members; + + while (mdef->type != -1) { + char *ptr1 = ((char *)data1) + mdef->struct_offset; + char *ptr2 = ((char *)data2) + mdef->struct_offset; + + switch (mdef->type) { + case REPLAY_STRUCT_PTR: { + void **vptr1 = (void **)ptr1; + void **vptr2 = (void **)ptr2; + + if (!*vptr1 || !*vptr2) { + printf("failed to load pointers %p %p\n", *vptr1, *vptr2); + mdef++; + continue; + } + + ptr1 = *vptr1; + ptr2 = *vptr2; + } + case REPLAY_STRUCT: + replay_load(mdef->sdef, ptr1, ptr2); + break; + case REPLAY_INT: + case REPLAY_FLOAT: + memcpy(ptr1, ptr2, sizeof(int)); + break; + case REPLAY_BYTE: + case REPLAY_BOOL: + *ptr1 = *ptr2; + break; + case REPLAY_VEC2: + memcpy(ptr1, ptr2, sizeof(float) * 2); + break; + case REPLAY_VEC3: + memcpy(ptr1, ptr2, sizeof(float) * 3); + break; + case REPLAY_VEC4: + memcpy(ptr1, ptr2, sizeof(float) * 4); + break; + case REPLAY_SHORT: + memcpy(ptr1, ptr2, 2); + break; + } + mdef++; + } +} + +void do_brush_action(struct Sculpt *sd, + struct Object *ob, + struct Brush *brush, + struct UnifiedPaintSettings *ups, + PaintModeSettings *paint_mode_settings); +void sculpt_combine_proxies(Sculpt *sd, Object *ob); +bool sculpt_tool_is_proxy_used(const char sculpt_tool); +void sculpt_stroke_update_step(bContext *C, struct PaintStroke *stroke, PointerRNA *itemptr); + +static void *hashco(float fx, float fy, float fz, float fdimen) +{ + double x = (double)fx; + double y = (double)fy; + double z = (double)fz; + double dimen = (double)fdimen; + + return (void *)((intptr_t)(z * dimen * dimen * dimen + y * dimen * dimen + x * dimen)); +} + +void SCULPT_replay_make_cube(struct bContext *C, int steps) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + + if (!ss || !ss->bm) { + return; + } + + GHash *vhash = BLI_ghash_ptr_new("vhash"); + + float df = 2.0f / (float)(steps - 1); + + int hashdimen = steps * 8; + + BMVert **grid = MEM_malloc_arrayN(steps * steps * 2, sizeof(*grid), "bmvert grid"); + BMesh *bm = ss->bm; + + BM_mesh_clear(bm); + + for (int side = 0; side < 6; side++) { + int axis = side >= 3 ? side - 3 : side; + float sign = side >= 3 ? -1.0f : 1.0f; + + printf("AXIS: %d\n", axis); + + float u = -1.0f; + + for (int i = 0; i < steps; i++, u += df) { + float v = -1.0f; + + for (int j = 0; j < steps; j++, v += df) { + float co[3]; + + co[axis] = u; + co[(axis + 1) % 3] = v; + co[(axis + 2) % 3] = sign; + + // turn into sphere + normalize_v3(co); + // mul_v3_fl(co, 2.0f); + + void *key = hashco(co[0], co[1], co[2], hashdimen); + +#if 0 + printf("%.3f %.3f %.3f, key: %p i: %d j: %d df: %f, u: %f v: %f\n", + co[0], + co[1], + co[2], + key, + i, + j, + df, + u, + v); +#endif + + void **val = NULL; + + if (!BLI_ghash_ensure_p(vhash, key, &val)) { + BMVert *v2 = BM_vert_create(bm, co, NULL, BM_CREATE_NOP); + + *val = (void *)v2; + } + + BMVert *v2 = (BMVert *)*val; + int idx = j * steps + i; + + grid[idx] = v2; + } + } + + for (int i = 0; i < steps - 1; i++) { + for (int j = 0; j < steps - 1; j++) { + int idx1 = j * steps + i; + int idx2 = (j + 1) * steps + i; + int idx3 = (j + 1) * steps + i + 1; + int idx4 = j * steps + i + 1; + + BMVert *v1 = grid[idx1]; + BMVert *v2 = grid[idx2]; + BMVert *v3 = grid[idx3]; + BMVert *v4 = grid[idx4]; + + if (v1 == v2 || v1 == v3 || v1 == v4 || v2 == v3 || v2 == v4 || v3 == v4) { + printf("ERROR!\n"); + continue; + } + + if (sign >= 0) { + BMVert *vs[4] = {v4, v3, v2, v1}; + BM_face_create_verts(bm, vs, 4, NULL, BM_CREATE_NOP, true); + } + else { + BMVert *vs[4] = {v1, v2, v3, v4}; + BM_face_create_verts(bm, vs, 4, NULL, BM_CREATE_NOP, true); + } + } + } + } + + MEM_SAFE_FREE(grid); + BLI_ghash_free(vhash, NULL, NULL); + +#if 1 + // randomize + uint *rands[4]; + uint tots[4] = {bm->totvert, bm->totedge, bm->totloop, bm->totface}; + + RNG *rng = BLI_rng_new(0); + + for (uint i = 0; i < 4; i++) { + rands[i] = MEM_malloc_arrayN(tots[i], sizeof(uint), "rands[i]"); + + for (uint j = 0; j < tots[i]; j++) { + rands[i][j] = j; + } + + for (uint j = 0; j < tots[i] >> 1; j++) { + int j2 = BLI_rng_get_int(rng) % tots[i]; + SWAP(uint, rands[i][j], rands[i][j2]); + } + } + + BM_mesh_remap(bm, rands[0], rands[1], rands[2], rands[3]); + + for (int i = 0; i < 4; i++) { + MEM_SAFE_FREE(rands[i]); + } + + BLI_rng_free(rng); +#endif + + bke::pbvh::free(ss->pbvh); + ss->pbvh = NULL; + + // XXX call BKE_sculptsession_update_attr_refs here? + + /* Redraw. */ + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, ND_DATA | NC_OBJECT | ND_DRAW, ob); +} + +void SCULPT_replay(struct bContext *C) +{ + Object *ob = CTX_data_active_object(C); + + if (!ob) { + printf("no object\n"); + return; + } + + Scene *scene = CTX_data_scene(C); + + if (!scene) { + printf("no scene\n"); + return; + } + + Sculpt *sd = scene->toolsettings->sculpt; + + if (!sd) { + printf("no sculpt settings\n"); + return; + } + + SculptSession *ss = ob->sculpt; + + if (!ss) { + printf("object must be in sculpt mode\n"); + return; + } + + if (!current_log) { + printf("%s: no reply data\n", __func__); + return; + } + + SculptReplayLog *log = current_log; + SculptBrushSample *samp = log->samples; + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + + bool have_cache = ss->cache; + ViewContext vc; + + log->is_playing = true; + float last_dyntopo_t = 0.0f; + + SCULPT_undo_push_begin_ex(ob, "Replay"); + + if (!have_cache) { + ED_view3d_viewcontext_init(C, &vc, depsgraph); + } + + for (int i = 0; i < log->totsample; i++, samp++) { + if (!have_cache) { + ss->cache = &samp->cache; + ss->cache->vc = &vc; + } + else { + replay_load(&StrokeCacheDef, &samp->cache, ss->cache); + } + + replay_load(&SculptDef, &samp->sd, sd); + replay_load( + &UnifiedPaintSettingsDef, &samp->ups, &scene->toolsettings->unified_paint_settings); + + ss->cache->first_time = i == 0; + samp->ups.last_stroke_valid = i > 0; + + Brush _brush = *ss->cache->brush; + Brush *brush = &_brush; + + samp->stroke.brush = brush; + samp->stroke.ups = &samp->ups; + samp->stroke.vc = vc; + samp->sd.paint.brush = brush; + + ss->cache->stroke = &samp->stroke; + + ss->cache->last_dyntopo_t = last_dyntopo_t; + + // XXX + // sculpt_stroke_update_step(C, ss->cache->stroke, NULL); + last_dyntopo_t = ss->cache->last_dyntopo_t; + continue; + do_brush_action(sd, + ob, + brush, + &scene->toolsettings->unified_paint_settings, + &scene->toolsettings->paint_mode); + sculpt_combine_proxies(sd, ob); + + /* Hack to fix noise texture tearing mesh. */ + // sculpt_fix_noise_tear(sd, ob); + + /* TODO(sergey): This is not really needed for the solid shading, + * which does use pBVH drawing anyway, but texture and wireframe + * requires this. + * + * Could be optimized later, but currently don't think it's so + * much common scenario. + * + * Same applies to the DEG_id_tag_update() invoked from + * sculpt_flush_update_step(). + */ + if (ss->deform_modifiers_active) { + SCULPT_flush_stroke_deform(sd, ob, sculpt_tool_is_proxy_used(brush->sculpt_tool)); + } + else if (ss->shapekey_active) { + // sculpt_update_keyblock(ob); + } + + ss->cache->first_time = false; + copy_v3_v3(ss->cache->true_last_location, ss->cache->true_location); + + /* Cleanup. */ + if (brush->sculpt_tool == SCULPT_TOOL_MASK) { + SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK); + } + else if (ELEM(brush->sculpt_tool, SCULPT_TOOL_PAINT, SCULPT_TOOL_SMEAR)) { + SCULPT_flush_update_step(C, SCULPT_UPDATE_COLOR); + } + else { + SCULPT_flush_update_step(C, SCULPT_UPDATE_COORDS); + } + + int update = SCULPT_UPDATE_COORDS | SCULPT_UPDATE_COLOR | SCULPT_UPDATE_VISIBILITY | + SCULPT_UPDATE_MASK; + SCULPT_flush_update_done(C, ob, update); + } + + if (!have_cache) { + ss->cache = NULL; + } + + SCULPT_undo_push_end(ob); + log->is_playing = false; +} + +void SCULPT_replay_parse(const char *buf) +{ + if (current_log) { + SCULPT_replay_log_end(); + } + + SculptReplayLog *log = current_log = SCULPT_replay_log_create(); + + int i = 0; + int n = 0; + int len = strlen(buf); + + SKIP_ALL_WS; + + int version = 0; + + sscanf(buf + i, "version:%d\n%n", &version, &n); + i += n; + + SKIP_ALL_WS; + + while (i < len) { + // find newline + + SKIP_WS; + + int end = strcspn(buf + i, "\n"); + if (end < 0) { + end = len - 1; // last line? + } + + if (end == 0) { + // empty line + i++; + continue; + } + + int nr = 0; + if (sscanf(buf + i, "samp:%d.%n", &nr, &n) == 0) { + i += end; + SKIP_ALL_WS; + continue; + } + i += n; + + log->totsample = MAX2(log->totsample, nr + 1); + replay_samples_ensure_size(log); + + SculptBrushSample *samp = log->samples + nr; + + if (!samp->cache.brush) { + samp->cache.brush = BLI_memarena_calloc(log->arena, sizeof(Brush)); + } + + i += parse_replay_member(buf + i, end, &SculptBrushSampleDef, samp); + + SKIP_ALL_WS; + } + + return; +} + +void replay_serialize_struct(ReplaySerializer *state, ReplaySerialStruct *def, void *struct_data) +{ + // DynStr *out = state->out; + + ReplaySerialDef *mdef = def->members; + char buf[256]; + + while (mdef->type >= 0) { + char *ptr = (char *)struct_data; + ptr += mdef->struct_offset; + + if (!ELEM(mdef->type, REPLAY_STRUCT, REPLAY_STRUCT_PTR)) { + replay_write_path(state, mdef->name); + } + + switch (mdef->type) { + case REPLAY_STRUCT: + case REPLAY_STRUCT_PTR: + replay_push_stack(state, mdef->name, mdef->type == REPLAY_STRUCT ? "." : "->"); + // BLI_dynstr_append(state->out, " {\n"); + if (mdef->type == REPLAY_STRUCT_PTR) { + replay_serialize_struct(state, mdef->sdef, *(void **)ptr); + } + else { + replay_serialize_struct(state, mdef->sdef, ptr); + } + replay_pop_stack(state); + // BLI_dynstr_append(state->out, "}\n"); + break; + case REPLAY_INT: + sprintf(buf, ": %d\n", *((int *)ptr)); + BLI_dynstr_append(state->out, buf); + break; + case REPLAY_FLOAT: + sprintf(buf, ": %f\n", *((float *)ptr)); + BLI_dynstr_append(state->out, buf); + break; + case REPLAY_VEC2: + sprintf(buf, ": [%f,%f]\n", ((float *)ptr)[0], ((float *)ptr)[1]); + BLI_dynstr_append(state->out, buf); + break; + case REPLAY_VEC3: + sprintf(buf, ": [%f,%f,%f]\n", ((float *)ptr)[0], ((float *)ptr)[1], ((float *)ptr)[2]); + BLI_dynstr_append(state->out, buf); + break; + case REPLAY_VEC4: + sprintf(buf, + ": [%f,%f,%f,%f]\n", + ((float *)ptr)[0], + ((float *)ptr)[1], + ((float *)ptr)[2], + ((float *)ptr)[3]); + BLI_dynstr_append(state->out, buf); + break; + case REPLAY_BOOL: + sprintf(buf, ": %s\n", *ptr ? "1" : "0"); + BLI_dynstr_append(state->out, buf); + break; + case REPLAY_BYTE: + sprintf(buf, ": %d\n", (int)*ptr); + BLI_dynstr_append(state->out, buf); + break; + case REPLAY_SHORT: + sprintf(buf, ": %d\n", (int)*((short *)ptr)); + BLI_dynstr_append(state->out, buf); + break; + } + + mdef++; + } +} + +void replay_state_init(ReplaySerializer *state) +{ + memset(state, 0, sizeof(*state)); + state->stack_head = -1; +} + +char *SCULPT_replay_serialize() +{ + if (!current_log) { + return ""; + } + + SculptReplayLog *log = current_log; + DynStr *out = BLI_dynstr_new(); + + ReplaySerializer state; + + BLI_dynstr_append(out, "version:1\n"); + + replay_state_init(&state); + state.out = out; + + for (int i = 0; i < log->totsample; i++) { + char buf[32]; + + sprintf(buf, "samp:%d", i); + replay_push_stack(&state, buf, "."); + + replay_serialize_struct(&state, &SculptBrushSampleDef, log->samples + i); + + replay_pop_stack(&state); + } + + char *ret = BLI_dynstr_get_cstring(out); + BLI_dynstr_free(out); + + return ret; +} + +static void SCULPT_replay_deserialize(SculptReplayLog *log) {} + +static void replay_samples_ensure_size(SculptReplayLog *log) +{ + if (log->totsample >= log->samples_size) { + int size = (2 + log->samples_size); + size += size >> 1; + + if (!log->samples) { + log->samples = MEM_calloc_arrayN(size, sizeof(*log->samples), "log->samples"); + } + else { + log->samples = MEM_recallocN(log->samples, sizeof(*log->samples) * size); + } + + log->samples_size = size; + } +} + +static bool replay_ensure_tex(SculptReplayLog *log, MTex *tex) +{ + if (!tex->tex) { + return true; + } + + for (int i = 0; i < log->tot_textures; i++) { + if (STREQ(log->textures[i]->id.name, tex->tex->id.name)) { + return true; + } + } + + Tex *texcpy = (Tex *)BLI_memarena_alloc(log->arena, sizeof(Tex)); + *texcpy = *tex->tex; + + tex->tex = texcpy; + + if (texcpy->ima) { + Image *ima = BLI_memarena_alloc(log->arena, sizeof(*ima)); + *ima = *texcpy->ima; + texcpy->ima = ima; + } + // if (texcpy->ima && texcpy->ima->id); + + return false; +} + +void SCULPT_replay_test() +{ + SculptSession ss = {0}; + Sculpt sd = {0}; + Object ob = {0}; + StrokeCache cache = {0}; + Brush brush = {0}; + + brush.size = 1.5f; + brush.weight = 2.0f; + brush.autosmooth_factor = 2.0f; + + ss.cache = &cache; + cache.bstrength = 1.0f; + cache.radius = 1.5f; + cache.brush = &brush; + + ss.active_vertex.i = -1LL; + ss.active_face.i = -1LL; + + SCULPT_replay_log_start(); + SCULPT_replay_log_append(&sd, &ss, &ob); + char *buf = SCULPT_replay_serialize(); + + if (buf) { + printf("=========result=======\n%s\n", buf); + } + + MEM_SAFE_FREE(buf); + SCULPT_replay_log_end(); +} + +void SCULPT_replay_log_append(Sculpt *sd, SculptSession *ss, Object *ob) +{ + SculptReplayLog *log = current_log; + + if (!log || log->is_playing) { + return; + } + + log->totsample++; + replay_samples_ensure_size(log); + + SculptBrushSample *samp = log->samples + log->totsample - 1; + + if (!ss->cache) { + printf("Error!!"); + return; + } + + samp->time = PIL_check_seconds_timer(); + samp->stroke = *ss->cache->stroke; + + samp->sd = *sd; + samp->cache = *ss->cache; + + // replay_ensure_tex(log, &samp->cache->brush.mtex); + + if (ss->active_vertex.i != -1LL) { + samp->have_active_vertex = true; + // copy_v3_v3(samp->active_vertex_co, SCULPT_vertex_co_get(ss, ss->active_vertex)); + } + else { + zero_v3(samp->active_vertex_co); + samp->have_active_vertex = false; + } + + // TODO: active face + samp->have_active_face = false; +} diff --git a/source/blender/editors/sculpt_paint/sculpt_smooth.cc b/source/blender/editors/sculpt_paint/sculpt_smooth.cc index 9956c3c51e7..030a835df04 100644 --- a/source/blender/editors/sculpt_paint/sculpt_smooth.cc +++ b/source/blender/editors/sculpt_paint/sculpt_smooth.cc @@ -8,11 +8,23 @@ #include "MEM_guardedalloc.h" +#include "BLI_alloca.h" #include "BLI_math_vector.h" +#include "BLI_math_vector_types.hh" +#include "BLI_set.hh" #include "BLI_task.h" +#include "BLI_timeit.hh" +#include "BLI_vector.hh" #include "DNA_brush_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "BKE_brush.hh" +#include "BKE_context.hh" +#include "BKE_global.hh" +#include "BKE_mesh.hh" +#include "BKE_mesh_mapping.hh" #include "BKE_paint.hh" #include "BKE_pbvh_api.hh" @@ -23,114 +35,743 @@ #include #include +using namespace blender; + +#define SMOOTH_FACE_CORNERS + namespace blender::ed::sculpt_paint::smooth { -void neighbor_coords_average_interior(SculptSession *ss, float result[3], PBVHVertRef vertex) +#if 0 +static void SCULPT_neighbor_coors_average_for_detail(SculptSession *ss, + float result[3], + PBVHVertRef vertex) + { - float avg[3] = {0.0f, 0.0f, 0.0f}; - int total = 0; + float detail = bke::pbvh::bmesh_detail_size_avg_get(ss->pbvh); + + float original_vertex_co[3]; + copy_v3_v3(original_vertex_co, SCULPT_vertex_co_get(ss, vertex)); + + float edge_length_accum = 0; int neighbor_count = 0; - const bool is_boundary = SCULPT_vertex_is_boundary(ss, vertex); + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { + edge_length_accum = len_v3v3(original_vertex_co, SCULPT_vertex_co_get(ss, ni.vertex)); + neighbor_count++; + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + + if (neighbor_count == 0) { + copy_v3_v3(result, original_vertex_co); + return; + } + + const float edge_length_avg = edge_length_accum / neighbor_count; + /* This ensures a common length average for all vertices. The smaller this factor is, the more + * uniform smoothing is going to be across different mesh detail areas, but it will make the + * smooth brush effect weaker. It can be exposed as a parameter in the future. */ + const float detail_factor = detail * 0.1f; + + float pos_accum[3] = {0.0f}; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { + float disp[3]; + sub_v3_v3v3(disp, SCULPT_vertex_co_get(ss, ni.vertex), original_vertex_co); + const float original_length = normalize_v3(disp); + float new_length = min_ff(original_length, detail_factor * original_length / edge_length_avg); + float new_co[3]; + madd_v3_v3v3fl(new_co, original_vertex_co, disp, new_length); + add_v3_v3(pos_accum, new_co); + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + mul_v3_v3fl(result, pos_accum, 1.0f / neighbor_count); +} +#endif + +template +static void SCULPT_neighbor_coords_average_interior_ex(SculptSession *ss, + float result[3], + PBVHVertRef vertex, + float projection, + float hard_corner_pin, + bool weighted, + eSculptBoundary bound_type, + eSculptCorner corner_type, + bool smooth_origco, + float factor) +{ + float3 avg(0.0f, 0.0f, 0.0f); + +#if 0 + if (weighted) { + SCULPT_neighbor_coors_average_for_detail(ss, result, vertex); + return; + } +#endif + + eSculptBoundary uvflag = ss->distort_correction_mode ? SCULPT_BOUNDARY_UV : SCULPT_BOUNDARY_NONE; + eSculptBoundary hard_flags = SCULPT_BOUNDARY_SHARP_MARK | SCULPT_BOUNDARY_SHARP_ANGLE | + SCULPT_BOUNDARY_MESH; + + hard_flags &= bound_type; + + if (ss->hard_edge_mode) { + hard_flags |= SCULPT_BOUNDARY_FACE_SET; + } + + bound_type |= uvflag; + + const eSculptBoundary is_boundary = SCULPT_vertex_is_boundary(ss, vertex, bound_type); + const eSculptCorner is_corner = SCULPT_vertex_is_corner(ss, vertex, corner_type); + + const float *(*vertex_co_get)(const SculptSession *ss, + PBVHVertRef vertex) = smooth_origco ? SCULPT_vertex_origco_get : + SCULPT_vertex_co_get; + void (*vertex_no_get)(const SculptSession *ss, + PBVHVertRef vertex, + float r_no[3]) = smooth_origco ? SCULPT_vertex_origno_get : + SCULPT_vertex_normal_get; + + float *areas = nullptr; + float3 no, co; + vertex_no_get(ss, vertex, no); + co = vertex_co_get(ss, vertex); + + const int valence = SCULPT_vertex_valence_get(ss, vertex); + + if (weighted) { + areas = reinterpret_cast(BLI_array_alloca(areas, valence)); + BKE_pbvh_get_vert_face_areas(ss->pbvh, vertex, areas, valence); + } + + float total = 0.0f; + int totboundary = 0; + + Vector ws; + Vector loops; + + bool is_bmesh = BKE_pbvh_type(ss->pbvh) == PBVH_BMESH; + auto addblock = [&](PBVHVertRef vertex2, PBVHEdgeRef edge2, float w) { + if (!is_bmesh) { + return; + } + BMVert *v = reinterpret_cast(vertex2.i); + BMEdge *e = reinterpret_cast(edge2.i); + + if (!e->l) { + return; + } + + BMLoop *l = e->l; + do { + BMLoop *l2 = l->v == v ? l : l->next; + + loops.append(l2); + ws.append(w); + } while ((l = l->radial_next) != e->l); + }; SculptVertexNeighborIter ni; SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { - neighbor_count++; + bool project_ok; + float w = weighted ? areas[ni.i] : 1.0f; + + eSculptBoundary is_boundary2; + + if (ni.has_edge) { + is_boundary2 = SCULPT_edge_is_boundary(ss, ni.edge, bound_type); + } + else { + is_boundary2 = SCULPT_vertex_is_boundary(ss, ni.vertex, bound_type); + } + + eSculptBoundary smooth_types = (!ss->hard_edge_mode ? + SCULPT_BOUNDARY_FACE_SET | SCULPT_BOUNDARY_SEAM : + SCULPT_BOUNDARY_SEAM) | + uvflag; + + if (is_boundary2) { + totboundary++; + } + + /* Boundary vertices use only other boundary vertices. */ if (is_boundary) { - /* Boundary vertices use only other boundary vertices. */ - if (SCULPT_vertex_is_boundary(ss, ni.vertex)) { - add_v3_v3(avg, SCULPT_vertex_co_get(ss, ni.vertex)); - total++; + project_ok = false; + + eSculptBoundary smooth_flag = is_boundary & smooth_types; + eSculptBoundary smooth_flag2 = is_boundary2 & smooth_types; + + /* Handle smooth boundaries. */ + if (smooth_flag && !(is_boundary & hard_flags) && bool(smooth_flag) != bool(smooth_flag2)) { + /* Project to plane. */ + float3 t1 = float3(vertex_co_get(ss, ni.vertex)) - co; + float fac = dot_v3v3(t1, no); + + float3 tco = (co + no * fac); + + if constexpr (smooth_face_corners) { + addblock(vertex, ni.edge, w); + } + + avg += tco * w; + total += w; + } + else if ((is_boundary & hard_flags) == (is_boundary2 & hard_flags)) { + avg += float3(vertex_co_get(ss, ni.vertex)) * w; + total += w; + project_ok = true; } } else { /* Interior vertices use all neighbors. */ - add_v3_v3(avg, SCULPT_vertex_co_get(ss, ni.vertex)); - total++; + avg += float3(vertex_co_get(ss, ni.vertex)) * w; + total += w; + project_ok = true; + } + + if constexpr (smooth_face_corners) { + if (project_ok) { + addblock(ni.vertex, ni.edge, w); + } } } SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); - /* Do not modify corner vertices. */ - if (neighbor_count <= 2 && is_boundary) { - copy_v3_v3(result, SCULPT_vertex_co_get(ss, vertex)); - return; + /* Ensure open strings of boundary edges don't shrink at the endpoints. */ + if (is_boundary && totboundary == 1) { + total = 0.0; + zero_v3(avg); + + if constexpr (smooth_face_corners) { + loops.clear(); + ws.clear(); + } + + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { + float w = weighted ? areas[ni.i] : 1.0f; + avg += float3(vertex_co_get(ss, ni.vertex)) * w; + total += w; + + if constexpr (smooth_face_corners) { + addblock(ni.vertex, ni.edge, w); + } + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); } /* Avoid division by 0 when there are no neighbors. */ if (total == 0) { - copy_v3_v3(result, SCULPT_vertex_co_get(ss, vertex)); + copy_v3_v3(result, vertex_co_get(ss, vertex)); return; } - mul_v3_v3fl(result, avg, 1.0f / total); + if constexpr (smooth_face_corners) { + if (is_bmesh && !smooth_origco) { + blender::bke::sculpt::interp_face_corners( + ss->pbvh, vertex, loops, ws, factor, ss->attrs.boundary_flags->bmesh_cd_offset); + } + } + + /* Project to plane if desired. */ + avg = avg / (float)total - co; + float t = dot_v3v3(avg, no); + + avg += -no * t * projection + co; + + if (is_corner) { + interp_v3_v3v3(result, co, avg, 1.0f - hard_corner_pin); + } + else { + copy_v3_v3(result, avg); + } } -void bmesh_four_neighbor_average(float avg[3], float direction[3], BMVert *v) +void neighbor_coords_average_interior(SculptSession *ss, + float result[3], + PBVHVertRef vertex, + float projection, + float hard_corner_pin, + bool use_area_weights, + bool smooth_origco, + float factor) { + eSculptBoundary bound_type = ss->smooth_boundary_flag; + eSculptCorner corner_type; + corner_type = eSculptCorner(int(bound_type & (SCULPT_BOUNDARY_MESH | SCULPT_BOUNDARY_SHARP_MARK | + SCULPT_BOUNDARY_SHARP_ANGLE)) + << SCULPT_CORNER_BIT_SHIFT); + + if (ss->hard_edge_mode && ss->smooth_boundary_flag & SCULPT_BOUNDARY_FACE_SET) { + corner_type |= SCULPT_CORNER_FACE_SET; + } + + if (ss->distort_correction_mode & UNDISTORT_RELAX_UVS) { + SCULPT_neighbor_coords_average_interior_ex(ss, + result, + vertex, + projection, + hard_corner_pin, + use_area_weights, + bound_type, + corner_type, + smooth_origco, + factor); + } + else { + SCULPT_neighbor_coords_average_interior_ex(ss, + result, + vertex, + projection, + hard_corner_pin, + use_area_weights, + bound_type, + corner_type, + smooth_origco, + factor); + } +} + +/* Compares four vectors seperated by 90 degrees around normal and picks the one closest + * to perpindicular to dir. Used for building a cross field. + */ +int closest_vec_to_perp(float dir[3], float r_dir2[3], float no[3], float *buckets, float w) +{ + int bits = 0; + + if (dot_v3v3(r_dir2, dir) < 0.0f) { + negate_v3(r_dir2); + bits |= 1; + } + + float dir4[3]; + cross_v3_v3v3(dir4, r_dir2, no); + normalize_v3(dir4); + + if (dot_v3v3(dir4, dir) < 0.0f) { + negate_v3(dir4); + bits |= 2; + } + + if (dot_v3v3(dir4, dir) > dot_v3v3(r_dir2, dir)) { + copy_v3_v3(r_dir2, dir4); + bits |= 4; + } + + buckets[bits] += w; + + return bits; +} + +void vec_transform(float r_dir2[3], float no[3], int bits) +{ + if (bits & 4) { + float dir4[3]; + + copy_v3_v3(dir4, r_dir2); + + if (bits & 2) { + negate_v3(dir4); + } + + float dir5[3]; + + cross_v3_v3v3(dir5, no, dir4); + normalize_v3(dir5); + + copy_v3_v3(r_dir2, dir5); + } + + if (bits & 1) { + negate_v3(r_dir2); + } +} + +void SCULPT_get_normal_average( + SculptSession *ss, float avg[3], PBVHVertRef vertex, bool weighted, bool use_original) +{ + int valence = SCULPT_vertex_valence_get(ss, vertex); + + float *areas = nullptr; + + if (weighted) { + areas = (float *)BLI_array_alloca(areas, valence * 2); + + BKE_pbvh_get_vert_face_areas(ss->pbvh, vertex, areas, valence); + } + + int total = 0; + zero_v3(avg); + + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { + float w = weighted ? areas[ni.i] : 1.0f; + float no2[3]; + + if (!use_original) { + SCULPT_vertex_normal_get(ss, ni.vertex, no2); + } + else { + SCULPT_vertex_origno_get(ss, ni.vertex, no2); + } + + madd_v3_v3fl(avg, no2, w); + total++; + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + + if (total) { + normalize_v3(avg); + } + else { + if (!use_original) { + SCULPT_vertex_normal_get(ss, ni.vertex, avg); + } + else { + SCULPT_vertex_origno_get(ss, ni.vertex, avg); + } + } +} + +void bmesh_four_neighbor_average(SculptSession *ss, + float avg[3], + float direction_in[3], + BMVert *v, + float projection, + float hard_corner_pin, + int cd_temp, + bool weighted, + bool do_origco, + float factor, + bool reproject_uvs) +{ float avg_co[3] = {0.0f, 0.0f, 0.0f}; float tot_co = 0.0f; - BMIter eiter; - BMEdge *e; + float buckets[8] = {0}; + PBVHVertRef vertex = {(intptr_t)v}; - BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) { - if (BM_edge_is_boundary(e)) { - copy_v3_v3(avg, v->co); - return; - } - BMVert *v_other = (e->v1 == v) ? e->v2 : e->v1; - float vec[3]; - sub_v3_v3v3(vec, v_other->co, v->co); - madd_v3_v3fl(vec, v->no, -dot_v3v3(vec, v->no)); - normalize_v3(vec); + float *field = BM_ELEM_CD_PTR(v, cd_temp); + float dir[3]; + float dir3[3] = {0.0f, 0.0f, 0.0f}; - /* fac is a measure of how orthogonal or parallel the edge is - * relative to the direction. */ - float fac = dot_v3v3(vec, direction); - fac = fac * fac - 0.5f; - fac *= fac; - madd_v3_v3fl(avg_co, v_other->co, fac); - tot_co += fac; + PBVH_CHECK_NAN4(field); + + float *areas = nullptr; + + SCULPT_vertex_check_origdata(ss, BKE_pbvh_make_vref(intptr_t(v))); + + float *origco = BM_ELEM_CD_PTR(v, ss->attrs.orig_co->bmesh_cd_offset); + float *origno = BM_ELEM_CD_PTR(v, ss->attrs.orig_no->bmesh_cd_offset); + + float direction[3]; + copy_v3_v3(direction, direction_in); + + if (do_origco) { + /* Project direction into original normal's plane. */ + madd_v3_v3fl(direction, origno, -dot_v3v3(origno, direction)); + normalize_v3(direction); + } + + eSculptBoundary boundary_mask = SCULPT_BOUNDARY_FACE_SET | SCULPT_BOUNDARY_MESH | + SCULPT_BOUNDARY_SHARP_MARK | SCULPT_BOUNDARY_SEAM | + SCULPT_BOUNDARY_UV; + eSculptBoundary boundary = SCULPT_vertex_is_boundary(ss, vertex, boundary_mask); + + eSculptCorner corner_mask = eSculptCorner(int(boundary) << SCULPT_CORNER_BIT_SHIFT); + eSculptCorner corner = SCULPT_vertex_is_corner(ss, vertex, corner_mask); + + eSculptBoundary smooth_mask = SCULPT_BOUNDARY_SEAM | SCULPT_BOUNDARY_UV; + if (!ss->hard_edge_mode) { + smooth_mask |= SCULPT_BOUNDARY_FACE_SET; + } + + float *co1 = do_origco ? origco : v->co; + float *no1 = do_origco ? origno : v->no; + + int valence = SCULPT_vertex_valence_get(ss, vertex); + + if (weighted) { + areas = (float *)BLI_array_alloca(areas, valence * 2); + + BKE_pbvh_get_vert_face_areas(ss->pbvh, vertex, areas, valence); + float totarea = 0.0f; + + for (int i = 0; i < valence; i++) { + totarea += areas[i]; + } + + totarea = totarea != 0.0f ? 1.0f / totarea : 0.0f; + + for (int i = 0; i < valence; i++) { + areas[i] *= totarea; + } + } + + /* Build final direction from input direction and the cross field. */ + copy_v3_v3(dir, field); + + if (dot_v3v3(dir, dir) == 0.0f) { + copy_v3_v3(dir, direction); + } + else { + closest_vec_to_perp(dir, direction, no1, buckets, 1.0f); + } + + float totdir3 = 0.0f; + + const float selfw = (float)valence * 0.0025f; + madd_v3_v3fl(dir3, direction, selfw); + + totdir3 += selfw; + + BMIter eiter; + BMEdge *e; + bool had_bound = false; + int area_i = 0; + int totboundary = 0; + + Vector loops; + Vector ws; + + auto addloop = [&](BMEdge *e, float w) { + if (!e->l) { + return; + } + + BMLoop *l = e->l; + l = l->v == v ? l->next : l; + + if (e->l->radial_next != e->l) { + w *= 0.5f; + + BMLoop *l2 = e->l->radial_next; + l2 = l2->v == v ? l2->next : l2; + + loops.append(l2); + ws.append(w); + } + + loops.append(l); + ws.append(w); + }; + + BM_ITER_ELEM_INDEX (e, &eiter, v, BM_EDGES_OF_VERT, area_i) { + BMVert *v_other = (e->v1 == v) ? e->v2 : e->v1; + PBVHVertRef vertex_other = {reinterpret_cast(v_other)}; + + float dir2[3]; + float *field2 = BM_ELEM_CD_PTR(v_other, cd_temp); + + float bucketw = 1.0f; + + float *co2; + + if (!do_origco || + blender::bke::sculpt::stroke_id_test_no_update(ss, vertex_other, STROKEID_USER_ORIGINAL)) + { + co2 = v_other->co; + } + else { + co2 = BM_ELEM_CD_PTR(v_other, ss->attrs.orig_co->bmesh_cd_offset); + } + + eSculptBoundary boundary2 = SCULPT_edge_is_boundary( + ss, BKE_pbvh_make_eref(intptr_t(e)), boundary_mask); + float dirw = 1.0f; + + PBVH_CHECK_NAN(no1); + PBVH_CHECK_NAN(dir2); + + /* Add to cross field. */ + if (boundary2 != SCULPT_BOUNDARY_NONE) { + had_bound = true; + + totboundary++; + + sub_v3_v3v3(dir2, co2, co1); + madd_v3_v3fl(dir2, no1, -dot_v3v3(no1, dir2)); + normalize_v3(dir2); + dirw = 100000.0f; + } + else { + dirw = field2[3]; + + copy_v3_v3(dir2, field2); + if (dot_v3v3(dir2, dir2) == 0.0f) { + copy_v3_v3(dir2, dir); + } + } + + closest_vec_to_perp(dir, dir2, no1, buckets, bucketw); + + madd_v3_v3fl(dir3, dir2, dirw); + totdir3 += dirw; + + if (boundary2) { + float fac = weighted ? areas[area_i] : 1.0f; + + madd_v3_v3fl(avg_co, co2, fac); + tot_co += fac; + + addloop(e, fac); + continue; + } + else if (boundary != SCULPT_BOUNDARY_NONE) { + if (boundary & smooth_mask) { + float fac = weighted ? areas[area_i] : 1.0f; + float vec[3], co3[3]; + + sub_v3_v3v3(vec, co2, co1); + copy_v3_v3(co3, co1); + madd_v3_v3fl(co3, no1, dot_v3v3(vec, no1)); + + madd_v3_v3fl(avg_co, co3, fac); + tot_co += fac; + + addloop(e, fac); + } + continue; + } + + float vec[3]; + sub_v3_v3v3(vec, co2, co1); + + /* Project into no1's plane. */ + madd_v3_v3fl(vec, no1, -dot_v3v3(vec, no1) * 1.0f); + normalize_v3(vec); + + /* Fac is a measure of how orthogonal or parallel the edge is + * relative to the direction. */ + float fac = dot_v3v3(vec, dir); + + fac = fac * fac - 0.5f; + fac *= fac; + + PBVH_CHECK_NAN1(fac); + PBVH_CHECK_NAN(dir); + PBVH_CHECK_NAN(vec); + + if (weighted) { + fac *= areas[area_i]; + } + + madd_v3_v3fl(avg_co, co2, fac); + tot_co += fac; + addloop(e, fac); + } + + if (totboundary == 1) { + BM_ITER_ELEM_INDEX (e, &eiter, v, BM_EDGES_OF_VERT, area_i) { + BMVert *v_other = (e->v1 == v) ? e->v2 : e->v1; + float fac = weighted ? areas[area_i] : 1.0f; + + madd_v3_v3fl(avg_co, v_other->co, fac); + tot_co += fac; + addloop(e, fac); + } } - /* In case vert has no Edge s. */ if (tot_co > 0.0f) { mul_v3_v3fl(avg, avg_co, 1.0f / tot_co); /* Preserve volume. */ float vec[3]; - sub_v3_v3(avg, v->co); - mul_v3_v3fl(vec, v->no, dot_v3v3(avg, v->no)); + sub_v3_v3(avg, co1); + mul_v3_v3fl(vec, no1, dot_v3v3(avg, no1) * projection); sub_v3_v3(avg, vec); - add_v3_v3(avg, v->co); + add_v3_v3(avg, co1); } else { - zero_v3(avg); + copy_v3_v3(avg, co1); + } + + if (reproject_uvs && tot_co > 0.0f && !(boundary & SCULPT_BOUNDARY_UV)) { + float totw = 0.0f; + for (float w : ws) { + totw += w; + } + for (int i = 0; i < ws.size(); i++) { + ws[i] /= totw; + } + + blender::bke::sculpt::interp_face_corners( + ss->pbvh, vertex, loops, ws, factor, ss->attrs.boundary_flags->bmesh_cd_offset); + } + + eSculptCorner corner_type = SCULPT_CORNER_MESH | SCULPT_CORNER_SHARP_MARK; + if (ss->hard_edge_mode) { + corner_type |= SCULPT_CORNER_FACE_SET; + } + + if (corner & corner_type) { + interp_v3_v3v3(avg, avg, SCULPT_vertex_co_get(ss, vertex), hard_corner_pin); + } + + PBVH_CHECK_NAN(avg); + + /* Do not update field when doing original coordinates. */ + if (do_origco) { + return; + } + + if (totdir3 > 0.0f) { + float outdir = totdir3 / (float)valence; + + // mul_v3_fl(dir3, 1.0 / totdir3); + normalize_v3(dir3); + if (had_bound) { + copy_v3_v3(field, dir3); + field[3] = 1000.0f; + } + else { + + mul_v3_fl(field, field[3]); + madd_v3_v3fl(field, dir3, outdir); + + field[3] = (field[3] + outdir) * 0.4; + normalize_v3(field); + } + + float maxb = 0.0f; + int bi = 0; + for (int i = 0; i < 8; i++) { + if (buckets[i] > maxb) { + maxb = buckets[i]; + bi = i; + } + } + + vec_transform(field, no1, bi); + PBVH_CHECK_NAN4(field); } } /* Generic functions for laplacian smoothing. These functions do not take boundary vertices into * account. */ -void neighbor_coords_average(SculptSession *ss, float result[3], PBVHVertRef vertex) +void neighbor_coords_average(SculptSession *ss, + float result[3], + PBVHVertRef vertex, + float projection, + float hard_corner_pin, + bool weighted, + float factor) { - float avg[3] = {0.0f, 0.0f, 0.0f}; - int total = 0; + eSculptCorner corner_type = SCULPT_CORNER_SHARP_MARK | SCULPT_CORNER_FACE_SET; + eSculptBoundary bound_type = SCULPT_BOUNDARY_SHARP_MARK | SCULPT_BOUNDARY_SEAM | + SCULPT_BOUNDARY_UV | SCULPT_BOUNDARY_FACE_SET; - SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { - add_v3_v3(avg, SCULPT_vertex_co_get(ss, ni.vertex)); - total++; - } - SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); - - if (total > 0) { - mul_v3_v3fl(result, avg, 1.0f / total); - } - else { - copy_v3_v3(result, SCULPT_vertex_co_get(ss, vertex)); - } + SCULPT_neighbor_coords_average_interior_ex(ss, + result, + vertex, + projection, + hard_corner_pin, + weighted, + bound_type, + corner_type, + false, + factor); } float neighbor_mask_average(SculptSession *ss, @@ -232,8 +873,12 @@ static void do_enhance_details_brush_task(Object *ob, &automask_data); float disp[3]; - madd_v3_v3v3fl(disp, vd.co, ss->cache->detail_directions[vd.index], fade); + float *detail_dir = blender::bke::paint::vertex_attr_ptr(vd.vertex, + ss->attrs.detail_directions); + madd_v3_v3v3fl(disp, vd.co, detail_dir, fade); SCULPT_clip(sd, ss, vd.co, disp); + + BKE_sculpt_sharp_boundary_flag_update(ss, vd.vertex); } BKE_pbvh_vertex_iter_end; } @@ -243,20 +888,35 @@ static void enhance_details_brush(Sculpt *sd, Object *ob, Span nodes SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); + smooth_undo_push(sd, ob, nodes, brush); + SCULPT_vertex_random_access_ensure(ss); SCULPT_boundary_info_ensure(ob); + float projection = brush->autosmooth_projection; + bool use_area_weights = brush->flag2 & BRUSH_SMOOTH_USE_AREA_WEIGHT; + float hard_corner_pin = BKE_brush_hard_corner_pin_get(ss->scene, brush); + if (SCULPT_stroke_is_first_brush_step(ss->cache)) { const int totvert = SCULPT_vertex_count_get(ss); - ss->cache->detail_directions = static_cast( - MEM_malloc_arrayN(totvert, sizeof(float[3]), "details directions")); + + if (!ss->attrs.detail_directions) { + SculptAttributeParams params = {}; + params.stroke_only = true; + + ss->attrs.detail_directions = BKE_sculpt_attribute_ensure( + ob, AttrDomain::Point, CD_PROP_FLOAT3, SCULPT_ATTRIBUTE_NAME(laplacian_disp), ¶ms); + } for (int i = 0; i < totvert; i++) { PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); float avg[3]; - neighbor_coords_average(ss, avg, vertex); - sub_v3_v3v3(ss->cache->detail_directions[i], avg, SCULPT_vertex_co_get(ss, vertex)); + neighbor_coords_average(ss, avg, vertex, projection, hard_corner_pin, use_area_weights); + float *detail_dir = blender::bke::paint::vertex_attr_ptr(vertex, + ss->attrs.detail_directions); + + sub_v3_v3v3(detail_dir, avg, SCULPT_vertex_co_get(ss, vertex)); } } @@ -267,15 +927,19 @@ static void enhance_details_brush(Sculpt *sd, Object *ob, Span nodes }); } -static void smooth_mask_node(Object *ob, - const Brush *brush, - const SculptMaskWriteInfo mask_write, - float bstrength, - PBVHNode *node) +static void do_smooth_brush_task(Object *ob, + Sculpt *sd, + const Brush *brush, + const bool smooth_mask, + const bool smooth_origco, + const SculptMaskWriteInfo mask_write, + float bstrength, + PBVHNode *node) { SculptSession *ss = ob->sculpt; PBVHVertexIter vd; + const bool do_reproject = need_reproject(ss); CLAMP(bstrength, 0.0f, 1.0f); @@ -287,126 +951,159 @@ static void smooth_mask_node(Object *ob, auto_mask::NodeData automask_data = auto_mask::node_begin( *ob, ss->cache->automasking.get(), *node); + float projection = brush->autosmooth_projection; + bool weighted = brush->flag2 & BRUSH_SMOOTH_USE_AREA_WEIGHT; + bool modified = false; + + float hard_corner_pin = BKE_brush_hard_corner_pin_get(ss->scene, brush); + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { if (!sculpt_brush_test_sq_fn(&test, vd.co)) { continue; } + modified = true; + auto_mask::node_update(automask_data, vd); - const float fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - 0.0f, - vd.vertex, - thread_id, - &automask_data); - float val = neighbor_mask_average(ss, mask_write, vd.vertex) - vd.mask; - val *= fade * bstrength; - float new_mask = vd.mask + val; - CLAMP(new_mask, 0.0f, 1.0f); + float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + smooth_mask ? 0.0f : vd.mask, + vd.vertex, + thread_id, + &automask_data); - SCULPT_mask_vert_set(BKE_pbvh_type(ss->pbvh), mask_write, new_mask, vd); - } - BKE_pbvh_vertex_iter_end; -} + if (smooth_mask) { + float val = neighbor_mask_average(ss, mask_write, vd.vertex) - vd.mask; + val *= fade * bstrength; + float new_mask = vd.mask + val; + CLAMP(new_mask, 0.0f, 1.0f); -void do_smooth_mask_brush(Sculpt *sd, Object *ob, Span nodes, float bstrength) -{ - SculptSession *ss = ob->sculpt; - const Brush *brush = BKE_paint_brush(&sd->paint); + SCULPT_mask_vert_set(BKE_pbvh_type(ss->pbvh), mask_write, new_mask, vd); + } + else { + float oldco[3]; + float oldno[3]; + copy_v3_v3(oldco, vd.co); + SCULPT_vertex_normal_get(ss, vd.vertex, oldno); - const int max_iterations = 4; - const float fract = 1.0f / max_iterations; + float avg[3], val[3]; + neighbor_coords_average_interior( + ss, avg, vd.vertex, projection, hard_corner_pin, weighted, false, fade); - CLAMP(bstrength, 0.0f, 1.0f); + if (smooth_origco) { + float origco_avg[3]; - const int count = int(bstrength * max_iterations); - const float last = max_iterations * (bstrength - count * fract); + neighbor_coords_average_interior( + ss, origco_avg, vd.vertex, projection, hard_corner_pin, weighted, true, fade); - SCULPT_vertex_random_access_ensure(ss); - SCULPT_boundary_info_ensure(ob); - - SculptMaskWriteInfo mask_write = SCULPT_mask_get_for_write(ss); - - for (const int iteration : IndexRange(count + 1)) { - const float strength = (iteration != count) ? 1.0f : last; - threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { - for (const int i : range) { - smooth_mask_node(ob, brush, mask_write, strength, nodes[i]); + float *origco = blender::bke::paint::vertex_attr_ptr(vd.vertex, ss->attrs.orig_co); + interp_v3_v3v3(origco, origco, origco_avg, fade); } - }); - } -} -static void smooth_position_node( - Object *ob, Sculpt *sd, const Brush *brush, float bstrength, PBVHNode *node) -{ - SculptSession *ss = ob->sculpt; + sub_v3_v3v3(val, avg, vd.co); + madd_v3_v3v3fl(val, vd.co, val, fade); + SCULPT_clip(sd, ss, vd.co, val); - PBVHVertexIter vd; + if (do_reproject) { + BKE_sculpt_reproject_cdata(ss, vd.vertex, oldco, oldno, ss->distort_correction_mode); + } - CLAMP(bstrength, 0.0f, 1.0f); - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, brush->falloff_shape); - - const int thread_id = BLI_task_parallel_thread_id(nullptr); - auto_mask::NodeData automask_data = auto_mask::node_begin( - *ob, ss->cache->automasking.get(), *node); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - if (!sculpt_brush_test_sq_fn(&test, vd.co)) { - continue; + BKE_sculpt_sharp_boundary_flag_update(ss, vd.vertex); } - - auto_mask::node_update(automask_data, vd); - - const float fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - vd.mask, - vd.vertex, - thread_id, - &automask_data); - - float avg[3], val[3]; - neighbor_coords_average_interior(ss, avg, vd.vertex); - sub_v3_v3v3(val, avg, vd.co); - madd_v3_v3v3fl(val, vd.co, val, fade); - SCULPT_clip(sd, ss, vd.co, val); } BKE_pbvh_vertex_iter_end; + + if (modified) { + BKE_pbvh_node_mark_update(node); + } } -void do_smooth_brush(Sculpt *sd, Object *ob, Span nodes, float bstrength) +void smooth_undo_push(Sculpt *sd, Object *ob, Span nodes, Brush *brush) { SculptSession *ss = ob->sculpt; - const Brush *brush = BKE_paint_brush(&sd->paint); + + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH && need_reproject(ss)) { + bool have_dyntopo = dyntopo::stroke_is_dyntopo(ss, sd, brush); + + for (PBVHNode *node : nodes) { + PBVHFaceIter fd; + + BKE_pbvh_face_iter_begin (ss->pbvh, node, fd) { + if (have_dyntopo) { + /* Always log face, uses more memory and is slower. */ + BM_log_face_modified(ss->bm, ss->bm_log, reinterpret_cast(fd.face.i)); + } + else { + /* Logs face once per stroke. */ + BM_log_face_if_modified(ss->bm, ss->bm_log, reinterpret_cast(fd.face.i)); + } + } + BKE_pbvh_face_iter_end(fd); + } + } +} + +void do_smooth_brush( + Sculpt *sd, Object *ob, Span nodes, float bstrength, const bool smooth_mask) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); const int max_iterations = 4; const float fract = 1.0f / max_iterations; + int iteration, count; + float last; + + SCULPT_boundary_info_ensure(ob); + smooth_undo_push(sd, ob, nodes, brush); + + /* PBVH_FACES needs ss->edge_to_face_map. */ + if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES && ss->edge_to_face_map.is_empty()) { + SCULPT_ensure_epmap(ss); + } CLAMP(bstrength, 0.0f, 1.0f); - const int count = int(bstrength * max_iterations); - const float last = max_iterations * (bstrength - count * fract); + count = int(bstrength * max_iterations); + last = max_iterations * (bstrength - count * fract); - SCULPT_vertex_random_access_ensure(ss); - SCULPT_boundary_info_ensure(ob); + if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES && ss->vert_to_face_map.is_empty()) { + BLI_assert_msg(0, "sculpt smooth: pmap missing"); + return; + } - for (const int iteration : IndexRange(count + 1)) { + SculptMaskWriteInfo mask_write; + if (smooth_mask) { + mask_write = SCULPT_mask_get_for_write(ss); + } + + for (iteration = 0; iteration <= count; iteration++) { const float strength = (iteration != count) ? 1.0f : last; + + if (brush->flag2 & BRUSH_SMOOTH_USE_AREA_WEIGHT) { + BKE_pbvh_flush_tri_areas(ob, ss->pbvh); + /* Preload face areas all at once into back buffer. */ + BKE_pbvh_face_areas_begin(ob, ss->pbvh); + threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { + for (const int i : range) { + BKE_pbvh_check_tri_areas(ss->pbvh, nodes[i]); + } + }); + + /* Swap into front buffer. */ + BKE_pbvh_face_areas_swap_buffers(ob, ss->pbvh); + } + + bool smooth_origco = SCULPT_tool_needs_smooth_origco(brush->sculpt_tool); threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { for (const int i : range) { - smooth_position_node(ob, sd, brush, strength, nodes[i]); + do_smooth_brush_task( + ob, sd, brush, smooth_mask, smooth_origco, mask_write, strength, nodes[i]); } }); } @@ -417,66 +1114,77 @@ void do_smooth_brush(Sculpt *sd, Object *ob, Span nodes) SculptSession *ss = ob->sculpt; /* NOTE: The enhance brush needs to initialize its state on the first brush step. The stroke - * strength can become 0 during the stroke, but it can not change sign (the sign is determined - * in the beginning of the stroke. So here it is important to not switch to enhance brush in the - * middle of the stroke. */ + * strength can become 0 during the stroke, but it can not change sign (the sign is + * determined in the beginning of the stroke. So here it is important to not switch to + * enhance brush in the middle of the stroke. */ if (ss->cache->bstrength < 0.0f) { /* Invert mode, intensify details. */ enhance_details_brush(sd, ob, nodes); } else { /* Regular mode, smooth. */ - do_smooth_brush(sd, ob, nodes, ss->cache->bstrength); + do_smooth_brush(sd, ob, nodes, ss->cache->bstrength, false); } } +void surface_smooth_laplacian_init(Object *ob) +{ + SculptAttributeParams params = {}; + + params.stroke_only = true; + + ob->sculpt->attrs.laplacian_disp = BKE_sculpt_attribute_ensure( + ob, AttrDomain::Point, CD_PROP_FLOAT3, SCULPT_ATTRIBUTE_NAME(laplacian_disp), ¶ms); +} + /* HC Smooth Algorithm. */ /* From: Improved Laplacian Smoothing of Noisy Surface Meshes */ void surface_smooth_laplacian_step(SculptSession *ss, float *disp, const float co[3], - float (*laplacian_disp)[3], const PBVHVertRef vertex, const float origco[3], - const float alpha) + const float alpha, + bool use_area_weights) { float laplacian_smooth_co[3]; float weigthed_o[3], weigthed_q[3], d[3]; - int v_index = BKE_pbvh_vertex_to_index(ss->pbvh, vertex); - neighbor_coords_average(ss, laplacian_smooth_co, vertex); + neighbor_coords_average(ss, laplacian_smooth_co, vertex, 0.0f, 0.0f, use_area_weights); mul_v3_v3fl(weigthed_o, origco, alpha); mul_v3_v3fl(weigthed_q, co, 1.0f - alpha); add_v3_v3v3(d, weigthed_o, weigthed_q); - sub_v3_v3v3(laplacian_disp[v_index], laplacian_smooth_co, d); + float *laplacian_disp = blender::bke::paint::vertex_attr_ptr(vertex, + ss->attrs.laplacian_disp); + + sub_v3_v3v3(laplacian_disp, laplacian_smooth_co, d); sub_v3_v3v3(disp, laplacian_smooth_co, co); } -void surface_smooth_displace_step(SculptSession *ss, - float *co, - float (*laplacian_disp)[3], - const PBVHVertRef vertex, - const float beta, - const float fade) +void surface_smooth_displace_step( + SculptSession *ss, float *co, const PBVHVertRef vertex, const float beta, const float fade) { float b_avg[3] = {0.0f, 0.0f, 0.0f}; float b_current_vertex[3]; int total = 0; SculptVertexNeighborIter ni; SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { - add_v3_v3(b_avg, laplacian_disp[ni.index]); + float *laplacian_disp = blender::bke::paint::vertex_attr_ptr(ni.vertex, + ss->attrs.laplacian_disp); + add_v3_v3(b_avg, laplacian_disp); total++; } SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); if (total > 0) { - int v_index = BKE_pbvh_vertex_to_index(ss->pbvh, vertex); + float *laplacian_disp = blender::bke::paint::vertex_attr_ptr(vertex, + ss->attrs.laplacian_disp); mul_v3_v3fl(b_current_vertex, b_avg, (1.0f - beta) / total); - madd_v3_v3fl(b_current_vertex, laplacian_disp[v_index], beta); + madd_v3_v3fl(b_current_vertex, laplacian_disp, beta); mul_v3_fl(b_current_vertex, clamp_f(fade, 0.0f, 1.0f)); sub_v3_v3(co, b_current_vertex); } @@ -500,8 +1208,10 @@ static void do_surface_smooth_brush_laplacian_task(Object *ob, const Brush *brus auto_mask::NodeData automask_data = auto_mask::node_begin( *ob, ss->cache->automasking.get(), *node); + bool weighted = brush->flag2 & BRUSH_SMOOTH_USE_AREA_WEIGHT; + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); + SCULPT_orig_vert_data_update(&orig_data, vd.vertex); if (!sculpt_brush_test_sq_fn(&test, vd.co)) { continue; } @@ -520,8 +1230,7 @@ static void do_surface_smooth_brush_laplacian_task(Object *ob, const Brush *brus &automask_data); float disp[3]; - surface_smooth_laplacian_step( - ss, disp, vd.co, ss->cache->surface_smooth_laplacian_disp, vd.vertex, orig_data.co, alpha); + surface_smooth_laplacian_step(ss, disp, vd.co, vd.vertex, orig_data.co, alpha, weighted); madd_v3_v3fl(vd.co, disp, clamp_f(fade, 0.0f, 1.0f)); } BKE_pbvh_vertex_iter_end; @@ -559,8 +1268,8 @@ static void do_surface_smooth_brush_displace_task(Object *ob, const Brush *brush vd.vertex, thread_id, &automask_data); - surface_smooth_displace_step( - ss, vd.co, ss->cache->surface_smooth_laplacian_disp, vd.vertex, beta, fade); + surface_smooth_displace_step(ss, vd.co, vd.vertex, beta, fade); + BKE_sculpt_sharp_boundary_flag_update(ss, vd.vertex); } BKE_pbvh_vertex_iter_end; } @@ -569,6 +1278,11 @@ void do_surface_smooth_brush(Sculpt *sd, Object *ob, Span nodes) { Brush *brush = BKE_paint_brush(&sd->paint); + SCULPT_boundary_info_ensure(ob); + smooth_undo_push(sd, ob, nodes, brush); + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, nodes.size()); for (int i = 0; i < brush->surface_smooth_iterations; i++) { threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { for (const int i : range) { diff --git a/source/blender/editors/sculpt_paint/sculpt_transform.cc b/source/blender/editors/sculpt_paint/sculpt_transform.cc index c61eebcaf05..bd4d0470f4c 100644 --- a/source/blender/editors/sculpt_paint/sculpt_transform.cc +++ b/source/blender/editors/sculpt_paint/sculpt_transform.cc @@ -50,6 +50,9 @@ void ED_sculpt_init_transform(bContext *C, SculptSession *ss = ob->sculpt; Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + /* flag update of original data */ + ss->stroke_id++; + copy_v3_v3(ss->init_pivot_pos, ss->pivot_pos); copy_v4_v4(ss->init_pivot_rot, ss->pivot_rot); copy_v3_v3(ss->init_pivot_scale, ss->pivot_scale); @@ -58,15 +61,17 @@ void ED_sculpt_init_transform(bContext *C, copy_v4_v4(ss->prev_pivot_rot, ss->pivot_rot); copy_v3_v3(ss->prev_pivot_scale, ss->pivot_scale); + ss->pivot_rot[3] = 1.0f; + undo::push_begin_ex(ob, undo_name); BKE_sculpt_update_object_for_edit(depsgraph, ob, false); - ss->pivot_rot[3] = 1.0f; - SCULPT_vertex_random_access_ensure(ss); filter::cache_init(C, ob, sd, undo::Type::Position, mval_fl, 5.0, 1.0f); + BKE_sculpt_update_object_for_edit(depsgraph, ob, false); + if (sd->transform_mode == SCULPT_TRANSFORM_MODE_RADIUS_ELASTIC) { ss->filter_cache->transform_displacement_mode = SCULPT_TRANSFORM_DISPLACEMENT_INCREMENTAL; } @@ -86,6 +91,7 @@ static void sculpt_transform_matrices_init(SculptSession *ss, transform_mat[4][4]; float start_pivot_pos[3], start_pivot_rot[4], start_pivot_scale[3]; + switch (t_mode) { case SCULPT_TRANSFORM_DISPLACEMENT_ORIGINAL: copy_v3_v3(start_pivot_pos, ss->init_pivot_pos); @@ -112,13 +118,13 @@ static void sculpt_transform_matrices_init(SculptSession *ss, /* Translation matrix. */ sub_v3_v3v3(d_t, ss->pivot_pos, start_pivot_pos); - SCULPT_flip_v3_by_symm_area(d_t, symm, v_symm, ss->init_pivot_pos); + SCULPT_flip_v3_by_symm_area(d_t, symm, v_symm, start_pivot_pos); translate_m4(t_mat, d_t[0], d_t[1], d_t[2]); /* Rotation matrix. */ sub_qt_qtqt(d_r, ss->pivot_rot, start_pivot_rot); normalize_qt(d_r); - SCULPT_flip_quat_by_symm_area(d_r, symm, v_symm, ss->init_pivot_pos); + SCULPT_flip_quat_by_symm_area(d_r, symm, v_symm, start_pivot_pos); quat_to_mat4(r_mat, d_r); /* Scale matrix. */ @@ -151,9 +157,10 @@ static void sculpt_transform_task(Object *ob, const float transform_mats[8][4][4 undo::push_node(ob, node, undo::Type::Position); BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); - float *start_co; + SCULPT_orig_vert_data_update(&orig_data, vd.vertex); + float transformed_co[3], orig_co[3], disp[3]; + float *start_co; float fade = vd.mask; copy_v3_v3(orig_co, orig_data.co); char symm_area = SCULPT_get_vertex_symm_area(orig_co); @@ -171,6 +178,7 @@ static void sculpt_transform_task(Object *ob, const float transform_mats[8][4][4 mul_m4_v3(transform_mats[int(symm_area)], transformed_co); sub_v3_v3v3(disp, transformed_co, start_co); mul_v3_fl(disp, 1.0f - fade); + add_v3_v3v3(vd.co, start_co, disp); } BKE_pbvh_vertex_iter_end; @@ -224,7 +232,7 @@ static void sculpt_elastic_transform_task(Object *ob, PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); + SCULPT_orig_vert_data_update(&orig_data, vd.vertex); float transformed_co[3], orig_co[3], disp[3]; const float fade = vd.mask; copy_v3_v3(orig_co, orig_data.co); @@ -334,7 +342,7 @@ void ED_sculpt_end_transform(bContext *C, Object *ob) using namespace blender::ed::sculpt_paint; SculptSession *ss = ob->sculpt; if (ss->filter_cache) { - filter::cache_free(ss); + filter::cache_free(ss, ob); } SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COORDS); } diff --git a/source/blender/editors/sculpt_paint/sculpt_undo.cc b/source/blender/editors/sculpt_paint/sculpt_undo.cc index d6c097724a9..d9250e4b6de 100644 --- a/source/blender/editors/sculpt_paint/sculpt_undo.cc +++ b/source/blender/editors/sculpt_paint/sculpt_undo.cc @@ -13,13 +13,13 @@ * the difference with the prior one. * * To use the sculpt undo system, you must call push_begin - * inside an operator exec or invoke callback (geometry_begin + * inside an operator exec or invoke callback (ED_Type::Geometry_begin * may be called if you wish to save a non-delta copy of the entire mesh). * This will initialize the sculpt undo stack and set up an undo step. * * At the end of the operator you should call push_end. * - * push_end and geometry_begin both take a + * push_end and ED_Type::Geometry_begin both take a * #wmOperatorType as an argument. There are _ex versions that allow a custom * name; try to avoid using them. These can break the redo panel since it requires * the undo push have the same name as the calling operator. @@ -31,6 +31,7 @@ */ #include +#include #include "MEM_guardedalloc.h" @@ -41,6 +42,8 @@ #include "BLI_utildefines.h" #include "DNA_key_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_screen_types.h" @@ -54,6 +57,7 @@ #include "BKE_layer.hh" #include "BKE_main.hh" #include "BKE_mesh.hh" +#include "BKE_mesh_runtime.hh" #include "BKE_multires.hh" #include "BKE_object.hh" #include "BKE_paint.hh" @@ -76,9 +80,13 @@ #include "ED_undo.hh" #include "bmesh.hh" +#include "bmesh_idmap.hh" +#include "bmesh_log.hh" #include "paint_intern.hh" #include "sculpt_intern.hh" +using blender::bke::dyntopo::DyntopoSet; + namespace blender::ed::sculpt_paint::undo { /* Uncomment to print the undo stack in the console on push/undo/redo. */ @@ -123,16 +131,17 @@ namespace blender::ed::sculpt_paint::undo { * End of dynamic topology and symmetrize in this mode are handled in a special * manner as well. */ -#define NO_ACTIVE_LAYER bke::AttrDomain::Auto +#define NO_ACTIVE_LAYER AttrDomain::Auto struct UndoSculpt { - Vector> nodes; + ListBase nodes; size_t undo_size; + BMLog *bm_restore; }; struct SculptAttrRef { - bke::AttrDomain domain; + AttrDomain domain; eCustomDataType type; char name[MAX_CUSTOMDATA_LAYER_NAME]; bool was_set; @@ -142,6 +151,13 @@ struct SculptUndoStep { UndoStep step; /* NOTE: will split out into list for multi-object-sculpt-mode. */ UndoSculpt data; + int id; + + bool auto_saved; + + // active vcol layer + SculptAttrRef active_attr_start; + SculptAttrRef active_attr_end; /* Active color attribute at the start of this undo step. */ SculptAttrRef active_color_start; @@ -156,10 +172,17 @@ struct SculptUndoStep { #endif }; -static UndoSculpt *get_nodes(); +static UndoSculpt *sculpt_undo_get_nodes(void); +static bool sculpt_attribute_ref_equals(SculptAttrRef *a, SculptAttrRef *b); static void sculpt_save_active_attribute(Object *ob, SculptAttrRef *attr); static UndoSculpt *sculpt_undosys_step_get_nodes(UndoStep *us_p); +static void update_unode_bmesh_memsize(Node *unode); +static UndoSculpt *sculpt_undo_get_nodes(void); +void sculpt_undo_print_nodes(void *active); +static bool check_first_undo_entry_dyntopo(Object *ob); +static void sculpt_undo_push_begin_ex(Object *ob, const char *name, bool no_first_entry_check); + #ifdef SCULPT_UNDO_DEBUG # ifdef _ # undef _ @@ -176,8 +199,7 @@ static char *undo_type_to_str(int type) _(Type::Geometry) _(Type::DyntopoSymmetrize) _(Type::FaceSet) - _(Type::HideVert) - _(Type::HideFace) + _(SCULPT_UNDO_HIDDEN) _(Type::Mask) _(Type::Color) default: @@ -197,7 +219,7 @@ static void print_sculpt_node(Object *ob, Node *node) } } -static void print_step(Object *ob, UndoStep *us, UndoStep *active, int i) +static void print_sculpt_undo_step(Object *ob, UndoStep *us, UndoStep *active, int i) { Node *node; @@ -235,7 +257,7 @@ static void print_step(Object *ob, UndoStep *us, UndoStep *active, int i) } } } -static void print_nodes(Object *ob, void *active) +void sculpt_undo_print_nodes(Object *ob, void *active) { printf("=================== Sculpt undo steps ==============\n"); @@ -253,7 +275,7 @@ static void print_nodes(Object *ob, void *active) printf("\n"); if (ustack->step_init) { printf("===Undo initialization stepB===\n"); - print_step(ob, ustack->step_init, active, -1); + print_sculpt_undo_step(ob, ustack->step_init, active, -1); printf("===============\n"); } @@ -263,18 +285,28 @@ static void print_nodes(Object *ob, void *active) act_i = i; } - print_step(ob, us, active, i); + print_sculpt_undo_step(ob, us, active, i); } if (ustack->step_active) { printf("\n\n==Active step:==\n"); - print_step(ob, ustack->step_active, active, act_i); + print_sculpt_undo_step(ob, ustack->step_active, active, act_i); } } #else -static void print_nodes(Object * /*ob*/, void * /*active*/) {} +# define sculpt_undo_print_nodes(ob, active) while (0) #endif +static void update_cb(PBVHNode *node, void *rebuild) +{ + BKE_pbvh_node_mark_update(node); + BKE_pbvh_node_mark_update_mask(node); + if (*((bool *)rebuild)) { + BKE_pbvh_vert_tag_update_normal_visibility(node); + } + BKE_pbvh_node_fully_hidden_set(node, 0); +} + struct PartialUpdateData { PBVH *pbvh; bool changed_position; @@ -350,6 +382,8 @@ static void update_modified_node_mesh(PBVHNode &node, PartialUpdateData &data) } } +static UndoSculpt *sculpt_undosys_step_get_nodes(UndoStep *us_p); + static void update_modified_node_grids(PBVHNode &node, PartialUpdateData &data) { const Span grid_indices = BKE_pbvh_node_get_grid_indices(node); @@ -675,79 +709,527 @@ static bool restore_face_sets(Object *ob, Node &unode, MutableSpan modifie return modified; } -static void bmesh_restore_generic(Node &unode, Object *ob, SculptSession *ss) +static int *sculpt_undo_get_indices32(Node *unode, int allvert) { - if (unode.applied) { - BM_log_undo(ss->bm, ss->bm_log); - unode.applied = false; - } - else { - BM_log_redo(ss->bm, ss->bm_log); - unode.applied = true; + int *indices = (int *)MEM_malloc_arrayN(allvert, sizeof(int), __func__); + + for (int i = 0; i < allvert; i++) { + indices[i] = (int)unode->vert_indices[i]; } - if (unode.type == Type::Mask) { - Vector nodes = bke::pbvh::search_gather(ss->pbvh, {}); - for (PBVHNode *node : nodes) { - BKE_pbvh_node_mark_redraw(node); + return indices; +} + +typedef struct BmeshUndoData { + Object *ob; + PBVH *pbvh; + BMesh *bm; + bool do_full_recalc; + bool balance_pbvh; + int cd_face_node_offset, cd_vert_node_offset; + int cd_face_node_offset_old, cd_vert_node_offset_old; + int cd_boundary_flag, cd_flags, cd_edge_boundary; + bool regen_all_unique_verts; + bool is_redo; +} BmeshUndoData; + +static void bmesh_undo_vert_update(BmeshUndoData *data, BMVert *v, bool triangulate = false) +{ + *BM_ELEM_CD_PTR(v, data->cd_flags) |= SCULPTFLAG_NEED_VALENCE; + if (triangulate) { + *BM_ELEM_CD_PTR(v, data->cd_flags) |= SCULPTFLAG_NEED_TRIANGULATE; + } + + BKE_sculpt_boundary_flag_update(data->ob->sculpt, {reinterpret_cast(v)}); +} + +static void bmesh_undo_on_vert_kill(BMVert *v, void *userdata) +{ + BmeshUndoData *data = (BmeshUndoData *)userdata; + int ni = BM_ELEM_CD_GET_INT(v, data->cd_vert_node_offset); + // data->do_full_recalc = true; + + bool bad = ni == -1 || !BKE_pbvh_get_node_leaf_safe(data->pbvh, ni); + + if (bad) { +#if 0 // not sure this is really an error + // something went wrong + printf("%s: error, vertex %d is not in pbvh; ni was: %d\n", + __func__, + BM_ELEM_GET_ID(data->bm, v), + ni); + // data->do_full_recalc = true; +#endif + return; + } + + BKE_pbvh_bmesh_remove_vertex(data->pbvh, v, false); + data->balance_pbvh = true; +} + +static void bmesh_undo_on_vert_add(BMVert *v, void *userdata) +{ + BmeshUndoData *data = (BmeshUndoData *)userdata; + + data->balance_pbvh = true; + + bmesh_undo_vert_update(data, v, true); + + /* Flag vert as unassigned to a PBVH node; it'll be added to pbvh when + * its owning faces are. + */ + BM_ELEM_CD_SET_INT(v, data->cd_vert_node_offset, -1); +} + +static void bmesh_undo_on_face_kill(BMFace *f, void *userdata) +{ + BmeshUndoData *data = (BmeshUndoData *)userdata; + int ni = BM_ELEM_CD_GET_INT(f, data->cd_face_node_offset); + + BKE_pbvh_bmesh_remove_face(data->pbvh, f, false); + + if (ni >= 0) { + PBVHNode *node = BKE_pbvh_get_node(data->pbvh, ni); + BKE_pbvh_bmesh_mark_node_regen(data->pbvh, node); + } + + BMLoop *l = f->l_first; + do { + bmesh_undo_vert_update(data, l->v, true); + } while ((l = l->next) != f->l_first); + + // data->do_full_recalc = true; + data->balance_pbvh = true; +} + +static void bmesh_undo_on_face_add(BMFace *f, void *userdata) +{ + BmeshUndoData *data = (BmeshUndoData *)userdata; + + BM_ELEM_CD_SET_INT(f, data->cd_face_node_offset, -1); + BKE_pbvh_bmesh_add_face(data->pbvh, f, false, true); + + int ni = BM_ELEM_CD_GET_INT(f, data->cd_face_node_offset); + PBVHNode *node = BKE_pbvh_get_node(data->pbvh, ni); + + BMLoop *l = f->l_first; + do { + bmesh_undo_vert_update(data, l->v, f->len > 3); + *BM_ELEM_CD_PTR(l->e, data->cd_edge_boundary) |= SCULPT_BOUNDARY_NEEDS_UPDATE | + SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE; + + int ni_l = BM_ELEM_CD_GET_INT(l->v, data->cd_vert_node_offset); + if (ni_l < 0 && ni >= 0) { + BM_ELEM_CD_SET_INT(l->v, ni_l, ni); + DyntopoSet &bm_unique_verts = BKE_pbvh_bmesh_node_unique_verts(node); + + bm_unique_verts.add(l->v); + } + } while ((l = l->next) != f->l_first); + + data->balance_pbvh = true; +} + +static void bmesh_undo_full_mesh(void *userdata) +{ + BmeshUndoData *data = (BmeshUndoData *)userdata; + + BKE_sculptsession_update_attr_refs(data->ob); + + if (data->pbvh) { + BMIter iter; + BMVert *v; + + BM_ITER_MESH (v, &iter, data->bm, BM_VERTS_OF_MESH) { + bmesh_undo_vert_update(data, v, true); + } + + data->pbvh = nullptr; + } + + /* Recalculate face normals to prevent tessellation errors.*/ + BM_mesh_normals_update(data->bm); + data->do_full_recalc = true; +} + +static void bmesh_undo_on_edge_change(BMEdge * /*v*/, + void * /*userdata*/, + void * /*old_customdata*/) +{ +} + +static void bmesh_undo_on_edge_kill(BMEdge *e, void *userdata) +{ + BmeshUndoData *data = (BmeshUndoData *)userdata; + + bmesh_undo_vert_update(data, e->v1, true); + bmesh_undo_vert_update(data, e->v2, true); +}; +; +static void bmesh_undo_on_edge_add(BMEdge *e, void *userdata) +{ + BmeshUndoData *data = (BmeshUndoData *)userdata; + + *BM_ELEM_CD_PTR(e, data->cd_edge_boundary) |= SCULPT_BOUNDARY_NEEDS_UPDATE | + SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE; + + bmesh_undo_vert_update(data, e->v1, true); + bmesh_undo_vert_update(data, e->v2, true); +} + +static void bmesh_undo_on_vert_change(BMVert *v, void *userdata, void *old_customdata) +{ + BmeshUndoData *data = (BmeshUndoData *)userdata; + + bmesh_undo_vert_update(data, v, true); + + if (!old_customdata) { + BM_ELEM_CD_SET_INT(v, data->cd_vert_node_offset, -1); + data->regen_all_unique_verts = true; + return; + } + + BMElem h; + h.head.data = old_customdata; + + int ni = BM_ELEM_CD_GET_INT(&h, data->cd_vert_node_offset); + + /* Attempt to find old node reference. */ + PBVHNode *node = BKE_pbvh_get_node_leaf_safe(data->pbvh, ni); + if (node) { + /* Make sure undo customdata didn't override node ref. */ + BKE_pbvh_node_mark_update(node); + BM_ELEM_CD_SET_INT(v, data->cd_vert_node_offset, ni); + } + else { + if (ni != DYNTOPO_NODE_NONE) { + printf("%s: error: corrupted vertex. ni: %d, cd_node_offset: %d\n", + __func__, + ni, + data->cd_vert_node_offset_old); + BM_ELEM_CD_SET_INT(v, data->cd_vert_node_offset, DYNTOPO_NODE_NONE); + } + + // data->regen_all_unique_verts = true; + } + + return; + // preserve pbvh node references + + int oldnode_i = BM_ELEM_CD_GET_INT(&h, data->cd_vert_node_offset); + + BM_ELEM_CD_SET_INT(v, data->cd_vert_node_offset, oldnode_i); + + if (oldnode_i >= 0) { + PBVHNode *node = BKE_pbvh_node_from_index(data->pbvh, oldnode_i); + BKE_pbvh_node_mark_update(node); + } +} + +static void bmesh_undo_on_face_change(BMFace *f, + void *userdata, + void *old_customdata, + char old_hflag) +{ + BmeshUndoData *data = (BmeshUndoData *)userdata; + + if (!old_customdata) { + data->do_full_recalc = true; // can't recover? + return; + } + + BMElem h; + h.head.data = old_customdata; + + int ni = BM_ELEM_CD_GET_INT(&h, data->cd_face_node_offset); + + BMLoop *l = f->l_first; + do { + *BM_ELEM_CD_PTR(l->e, data->cd_edge_boundary) |= SCULPT_BOUNDARY_NEEDS_UPDATE | + SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE; + *BM_ELEM_CD_PTR(l->v, data->cd_boundary_flag) |= SCULPT_BOUNDARY_NEEDS_UPDATE | + SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE; + } while ((l = l->next) != f->l_first); + + // attempt to find old node in old_customdata + PBVHNode *node = BKE_pbvh_get_node_leaf_safe(data->pbvh, ni); + if (node) { + BM_ELEM_CD_SET_INT(f, data->cd_face_node_offset, ni); + BKE_pbvh_node_mark_update(node); + + if ((old_hflag & BM_ELEM_HIDDEN) != (f->head.hflag & BM_ELEM_HIDDEN)) { + BKE_pbvh_node_mark_update_visibility(node); } } else { + printf("pbvh face undo error\n"); + data->do_full_recalc = true; + BM_ELEM_CD_SET_INT(f, data->cd_face_node_offset, -1); + } +} + +static void update_unode_bmesh_memsize(Node *unode) +{ + // update memory size + UndoSculpt *usculpt = sculpt_undo_get_nodes(); + + if (!usculpt) { + return; + } + + // subtract old size + if (usculpt->undo_size >= unode->undo_size) { + usculpt->undo_size -= unode->undo_size; + } + + unode->undo_size = BM_log_entry_size(unode->bm_entry); + // printf("unode->unode_size: size: %.4fmb\n", __func__, float(unode->undo_size) / 1024.0f / + // 1024.0f); + + // add new size + usculpt->undo_size += unode->undo_size; +} + +static void bmesh_undo_customdata_change(CustomData *domain, char htype, void *userdata) +{ + BmeshUndoData *data = (BmeshUndoData *)userdata; + + if (htype == BM_VERT) { + data->cd_vert_node_offset_old = CustomData_get_offset_named( + domain, CD_PROP_INT32, SCULPT_ATTRIBUTE_NAME(dyntopo_node_id_vertex)); + } + else if (htype == BM_FACE) { + data->cd_face_node_offset_old = CustomData_get_offset_named( + domain, CD_PROP_INT32, SCULPT_ATTRIBUTE_NAME(dyntopo_node_id_face)); + } +} + +static void sculpt_undo_bmesh_restore_generic(Node *unode, Object *ob, SculptSession *ss) +{ + BmeshUndoData data = {}; + data.ob = ob; + data.bm = ss->bm; + data.pbvh = ss->pbvh; + data.cd_face_node_offset = ss->cd_face_node_offset; + data.cd_vert_node_offset = ss->cd_vert_node_offset; + data.cd_face_node_offset_old = data.cd_vert_node_offset_old = -1; + data.cd_boundary_flag = ss->attrs.boundary_flags->bmesh_cd_offset; + data.cd_edge_boundary = ss->attrs.edge_boundary_flags->bmesh_cd_offset; + data.cd_flags = ss->attrs.flags->bmesh_cd_offset; + data.is_redo = !unode->applied; + + BMLogCallbacks callbacks = {bmesh_undo_on_vert_add, + bmesh_undo_on_vert_kill, + bmesh_undo_on_vert_change, + bmesh_undo_on_edge_add, + bmesh_undo_on_edge_kill, + bmesh_undo_on_edge_change, + bmesh_undo_on_face_add, + bmesh_undo_on_face_kill, + bmesh_undo_on_face_change, + bmesh_undo_full_mesh, + bmesh_undo_customdata_change, + (void *)&data}; + + BKE_sculptsession_update_attr_refs(ob); + BKE_sculpt_ensure_idmap(ob); + + if (unode->applied) { + BM_log_undo(ss->bm, ss->bm_log, &callbacks); + unode->applied = false; + } + else { + BM_log_redo(ss->bm, ss->bm_log, &callbacks); + unode->applied = true; + } + + update_unode_bmesh_memsize(unode); + + if (!data.do_full_recalc) { + BKE_pbvh_bmesh_check_nodes(ss->pbvh); + + Vector nodes = blender::bke::pbvh::search_gather(ss->pbvh, {}); + + if (data.regen_all_unique_verts) { + for (PBVHNode *node : nodes) { + BKE_pbvh_bmesh_mark_node_regen(ss->pbvh, node); + } + } + + BKE_pbvh_bmesh_regen_node_verts(ss->pbvh, false); + bke::pbvh::update_bounds(*ss->pbvh, PBVH_UpdateBB | PBVH_UpdateOriginalBB | PBVH_UpdateRedraw); + + if (data.balance_pbvh) { + blender::bke::dyntopo::after_stroke(ss->pbvh, true); + } + } + else { + printf("undo triggered pbvh rebuild"); SCULPT_pbvh_clear(ob); } } +static void sculpt_unode_bmlog_ensure(SculptSession *ss, Node *unode) +{ + if (!ss->bm_log && ss->bm && unode->bm_entry) { + ss->bm_log = BM_log_from_existing_entries_create(ss->bm, ss->bm_idmap, unode->bm_entry); + + if (!unode->applied) { + BM_log_undo_skip(ss->bm, ss->bm_log); + } + + if (ss->pbvh) { + BKE_pbvh_set_bm_log(ss->pbvh, ss->bm_log); + } + } +} + /* Create empty sculpt BMesh and enable logging. */ -static void bmesh_enable(Object *ob, Node &unode) +static void sculpt_undo_bmesh_enable(Object *ob, Node *unode, bool /*is_redo*/) { SculptSession *ss = ob->sculpt; - Mesh *mesh = static_cast(ob->data); + Mesh *me = static_cast(ob->data); SCULPT_pbvh_clear(ob); + if (ss->bm_idmap) { + BM_idmap_destroy(ss->bm_idmap); + ss->bm_idmap = nullptr; + } + + ss->active_face.i = ss->active_vertex.i = PBVH_REF_NONE; + /* Create empty BMesh and enable logging. */ - BMeshCreateParams bmesh_create_params{}; - bmesh_create_params.use_toolflags = false; + ss->bm = SCULPT_dyntopo_empty_bmesh(); - ss->bm = BM_mesh_create(&bm_mesh_allocsize_default, &bmesh_create_params); - BM_data_layer_add_named(ss->bm, &ss->bm->vdata, CD_PROP_FLOAT, ".sculpt_mask"); + BMeshFromMeshParams params = {0}; + params.use_shapekey = true; + params.active_shapekey = ob->shapenr; - mesh->flag |= ME_SCULPT_DYNAMIC_TOPOLOGY; + BM_mesh_bm_from_me(ss->bm, me, ¶ms); - /* Restore the BMLog using saved entries. */ - ss->bm_log = BM_log_from_existing_entries_create(ss->bm, unode.bm_entry); -} + /* Calculate normals. */ + BM_mesh_normals_update(ss->bm); -static void bmesh_restore_begin(bContext *C, Node &unode, Object *ob, SculptSession *ss) -{ - if (unode.applied) { - dyntopo::disable(C, &unode); - unode.applied = false; + /* Ensure mask. */ + if (!CustomData_has_layer_named(&ss->bm->vdata, CD_PROP_FLOAT, ".sculpt_mask")) { + BM_data_layer_add_named(ss->bm, &ss->bm->vdata, CD_PROP_FLOAT, ".sculpt_mask"); + } + + BKE_sculptsession_update_attr_refs(ob); + + if (ss->pbvh) { + blender::bke::paint::load_all_original(ob); + } + + BKE_sculpt_ensure_idmap(ob); + + if (!ss->bm_log) { + sculpt_unode_bmlog_ensure(ss, unode); } else { - bmesh_enable(ob, unode); + BM_log_set_idmap(ss->bm_log, ss->bm_idmap); + } - /* Restore the mesh from the first log entry. */ - BM_log_redo(ss->bm, ss->bm_log); + SCULPT_update_all_valence_boundary(ob); - unode.applied = true; + me->flag |= ME_SCULPT_DYNAMIC_TOPOLOGY; +} + +static void sculpt_undo_bmesh_restore_begin( + bContext *C, Node *unode, Object *ob, SculptSession *ss, int dir) +{ + if (unode->applied) { + if (ss->bm && ss->bm_log) { +#if 0 + if (dir == 1) { + BM_log_redo_skip(ss->bm, ss->bm_log); + } + else { + BM_log_undo_skip(ss->bm, ss->bm_log); + } +#else + if (dir == 1) { + BM_log_redo(ss->bm, ss->bm_log, nullptr); + } + else { + BM_log_undo(ss->bm, ss->bm_log, nullptr); + } +#endif + } + + BKE_pbvh_bmesh_check_nodes(ss->pbvh); + dyntopo::disable(C, unode); + unode->applied = false; + } + else { + /* Load bmesh from mesh data. */ + sculpt_undo_bmesh_enable(ob, unode, true); + + if (dir == 1) { + BM_log_redo(ss->bm, ss->bm_log, nullptr); + } + else { + BM_log_undo(ss->bm, ss->bm_log, nullptr); + } + + unode->applied = true; + } + + if (ss->bm) { + BM_mesh_elem_index_ensure(ss->bm, BM_VERT | BM_FACE); } } -static void bmesh_restore_end(bContext *C, Node &unode, Object *ob, SculptSession *ss) +static void sculpt_undo_bmesh_restore_end( + bContext *C, Node *unode, Object *ob, SculptSession *ss, int dir) { - if (unode.applied) { - bmesh_enable(ob, unode); - /* Restore the mesh from the last log entry. */ - BM_log_undo(ss->bm, ss->bm_log); + if (unode->applied) { + /*load bmesh from mesh data*/ + sculpt_undo_bmesh_enable(ob, unode, false); - unode.applied = false; + if (dir == -1) { + BM_log_undo(ss->bm, ss->bm_log, nullptr); + } + else { + BM_log_redo(ss->bm, ss->bm_log, nullptr); + } + + SCULPT_pbvh_clear(ob); + BKE_sculptsession_update_attr_refs(ob); + + BMIter iter; + BMVert *v; + BMFace *f; + + BM_ITER_MESH (v, &iter, ss->bm, BM_VERTS_OF_MESH) { + BM_ELEM_CD_SET_INT(v, ss->cd_vert_node_offset, DYNTOPO_NODE_NONE); + } + BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { + BM_ELEM_CD_SET_INT(f, ss->cd_face_node_offset, DYNTOPO_NODE_NONE); + } + + unode->applied = false; } else { + if (ss->bm && ss->bm_log) { + + if (dir == -1) { + BM_log_undo_skip(ss->bm, ss->bm_log); + } + else { + BM_log_redo_skip(ss->bm, ss->bm_log); + } + } + /* Disable dynamic topology sculpting. */ dyntopo::disable(C, nullptr); - unode.applied = true; + unode->applied = true; + } + + if (ss->bm) { + BM_mesh_elem_index_ensure(ss->bm, BM_VERT | BM_FACE); } } @@ -829,25 +1311,56 @@ static void restore_geometry(Node &unode, Object *object) * * Returns true if this was a dynamic-topology undo step, otherwise * returns false to indicate the non-dyntopo code should run. */ -static int bmesh_restore(bContext *C, Node &unode, Object *ob, SculptSession *ss) +static int sculpt_undo_bmesh_restore( + bContext *C, Node *unode, Object *ob, SculptSession *ss, int dir) { - switch (unode.type) { - case Type::DyntopoBegin: - bmesh_restore_begin(C, unode, ob, ss); - return true; + // handle transition from another undo type + ss->needs_flush_to_id = 1; + sculpt_unode_bmlog_ensure(ss, unode); + + bool ret = false; + + switch (unode->type) { + case Type::DyntopoBegin: + sculpt_undo_bmesh_restore_begin(C, unode, ob, ss, dir); + SCULPT_vertex_random_access_ensure(ss); + + ss->active_face.i = ss->active_vertex.i = PBVH_REF_NONE; + + ret = true; + break; case Type::DyntopoEnd: - bmesh_restore_end(C, unode, ob, ss); - return true; + ss->active_face.i = ss->active_vertex.i = PBVH_REF_NONE; + + sculpt_undo_bmesh_restore_end(C, unode, ob, ss, dir); + SCULPT_vertex_random_access_ensure(ss); + + ret = true; + break; default: if (ss->bm_log) { - bmesh_restore_generic(unode, ob, ss); - return true; + sculpt_undo_bmesh_restore_generic(unode, ob, ss); + SCULPT_vertex_random_access_ensure(ss); + + if (unode->type == Type::HideVert || unode->type == Type::HideFace) { + bke::pbvh::update_vertex_data(*ss->pbvh, PBVH_UpdateVisibility); + } + ret = true; } break; } - return false; + if (ss->pbvh && BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + BKE_pbvh_flush_tri_areas(ob, ss->pbvh); + } + + ss->active_face.i = ss->active_vertex.i = PBVH_REF_NONE; + + /* Load attribute layout from bmesh to ob. */ + BKE_sculptsession_sync_attributes(ob, static_cast(ob->data), true); + + return ret; } /* Geometry updates (such as Apply Base, for example) will re-evaluate the object and refine its @@ -863,7 +1376,10 @@ static int bmesh_restore(bContext *C, Node &unode, Object *ob, SculptSession *ss * * Note that the dependency graph is ensured to be evaluated prior to the undo step is decoded, * so if the object's modifier stack references other object it is all fine. */ -static void refine_subdiv(Depsgraph *depsgraph, SculptSession *ss, Object *object, Subdiv *subdiv) +static void sculpt_undo_refine_subdiv(Depsgraph *depsgraph, + SculptSession *ss, + Object *object, + Subdiv *subdiv) { Array deformed_verts = BKE_multires_create_deformed_base_mesh_vert_coords( depsgraph, object, ss->multires.modifier); @@ -873,7 +1389,7 @@ static void refine_subdiv(Depsgraph *depsgraph, SculptSession *ss, Object *objec reinterpret_cast(deformed_verts.data())); } -static void restore_list(bContext *C, Depsgraph *depsgraph, UndoSculpt &usculpt) +static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase *lb, int dir) { Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); @@ -882,9 +1398,12 @@ static void restore_list(bContext *C, Depsgraph *depsgraph, UndoSculpt &usculpt) Object *ob = BKE_view_layer_active_object_get(view_layer); SculptSession *ss = ob->sculpt; SubdivCCG *subdiv_ccg = ss->subdiv_ccg; + bool need_mask = false; + // bool did_first_hack = false; bool clear_automask_cache = false; - for (const std::unique_ptr &unode : usculpt.nodes) { + + LISTBASE_FOREACH (Node *, unode, lb) { if (!ELEM(unode->type, Type::Color, Type::Mask)) { clear_automask_cache = true; } @@ -892,22 +1411,34 @@ static void restore_list(bContext *C, Depsgraph *depsgraph, UndoSculpt &usculpt) /* Restore pivot. */ copy_v3_v3(ss->pivot_pos, unode->pivot_pos); copy_v3_v3(ss->pivot_rot, unode->pivot_rot); + if (STREQ(unode->idname, ob->id.name)) { + if (unode->type == Type::Mask) { + /* Is possible that we can't do the mask undo (below) + * because of the vertex count. */ + need_mask = true; + break; + } + } } if (clear_automask_cache) { ss->last_automasking_settings_hash = 0; } - if (!usculpt.nodes.is_empty()) { + DEG_id_tag_update(&ob->id, ID_RECALC_SHADING); + + sculpt_undo_print_nodes(ob, nullptr); + + if (lb->first != nullptr) { /* Only do early object update for edits if first node needs this. * Undo steps like geometry does not need object to be updated before they run and will * ensure object is updated after the node is handled. */ - const Node *first_unode = usculpt.nodes.first().get(); - if (first_unode->type != Type::Geometry) { + const Node *first_unode = (const Node *)lb->first; + if (!ELEM(first_unode->type, Type::Geometry, Type::DyntopoBegin, Type::DyntopoSymmetrize)) { BKE_sculpt_update_object_for_edit(depsgraph, ob, false); } - if (bmesh_restore(C, *usculpt.nodes.first(), ob, ss)) { + if (sculpt_undo_bmesh_restore(C, (Node *)lb->first, ob, ss, dir)) { return; } } @@ -932,14 +1463,17 @@ static void restore_list(bContext *C, Depsgraph *depsgraph, UndoSculpt &usculpt) Vector modified_verts_color; Vector modified_faces_face_set; Vector modified_grids; - for (std::unique_ptr &unode : usculpt.nodes) { + + LISTBASE_FOREACH (Node *, unode, lb) { if (!STREQ(unode->idname, ob->id.name)) { continue; } - /* Check if undo data matches current data well enough to continue. */ + /* Check if undo data matches current data well enough to + * continue. */ if (unode->mesh_verts_num) { if (ss->totvert != unode->mesh_verts_num) { + printf("error! %s\n", __func__); continue; } } @@ -953,59 +1487,84 @@ static void restore_list(bContext *C, Depsgraph *depsgraph, UndoSculpt &usculpt) use_multires_undo = true; } - switch (unode->type) { - case Type::Position: - modified_verts_position.resize(ss->totvert, false); - if (restore_coords(C, ob, depsgraph, *unode, modified_verts_position)) { - changed_position = true; - } - break; - case Type::HideVert: - modified_verts_hide.resize(ss->totvert, false); - if (restore_hidden(ob, *unode, modified_verts_hide)) { - changed_hide_vert = true; - } - break; - case Type::HideFace: - modified_faces_hide.resize(ss->totfaces, false); - if (restore_hidden_face(*ob, *unode, modified_faces_hide)) { - changed_hide_face = true; - } - break; - case Type::Mask: - modified_verts_mask.resize(ss->totvert, false); - if (restore_mask(ob, *unode, modified_verts_mask)) { - changed_mask = true; - } - break; - case Type::FaceSet: - modified_faces_face_set.resize(ss->totfaces, false); - if (restore_face_sets(ob, *unode, modified_faces_face_set)) { - changed_face_sets = true; - } - break; - case Type::Color: - modified_verts_color.resize(ss->totvert, false); - if (restore_color(ob, *unode, modified_verts_color)) { - changed_color = true; - } - break; - case Type::Geometry: - restore_geometry(*unode, ob); - changed_all_geometry = true; - BKE_sculpt_update_object_for_edit(depsgraph, ob, false); - break; + LISTBASE_FOREACH (Node *, unode, lb) { + if (!STREQ(unode->idname, ob->id.name)) { + continue; + } - case Type::DyntopoBegin: - case Type::DyntopoEnd: - case Type::DyntopoSymmetrize: - BLI_assert_msg(0, "Dynamic topology should've already been handled"); - break; + /* Check if undo data matches current data well enough to continue. */ + if (unode->mesh_verts_num) { + if (ss->totvert != unode->mesh_verts_num) { + continue; + } + } + else if (unode->maxgrid && subdiv_ccg != nullptr) { + if ((subdiv_ccg->grids.size() != unode->maxgrid) || + (subdiv_ccg->grid_size != unode->gridsize)) + { + continue; + } + + use_multires_undo = true; + } + + switch (unode->type) { + case Type::None: + BLI_assert_unreachable(); + break; + case Type::Position: + modified_verts_position.resize(ss->totvert, false); + if (restore_coords(C, ob, depsgraph, *unode, modified_verts_position)) { + changed_position = true; + } + break; + case Type::HideVert: + modified_verts_hide.resize(ss->totvert, false); + if (restore_hidden(ob, *unode, modified_verts_hide)) { + changed_hide_vert = true; + } + break; + case Type::HideFace: + modified_faces_hide.resize(ss->totfaces, false); + if (restore_hidden_face(*ob, *unode, modified_faces_hide)) { + changed_hide_face = true; + } + break; + case Type::Mask: + modified_verts_mask.resize(ss->totvert, false); + if (restore_mask(ob, *unode, modified_verts_mask)) { + changed_mask = true; + } + break; + case Type::FaceSet: + modified_faces_face_set.resize(ss->totfaces, false); + if (restore_face_sets(ob, *unode, modified_faces_face_set)) { + changed_face_sets = true; + } + break; + case Type::Color: + modified_verts_color.resize(ss->totvert, false); + if (restore_color(ob, *unode, modified_verts_color)) { + changed_color = true; + } + break; + case Type::Geometry: + restore_geometry(*unode, ob); + changed_all_geometry = true; + BKE_sculpt_update_object_for_edit(depsgraph, ob, false); + break; + + case Type::DyntopoBegin: + case Type::DyntopoEnd: + case Type::DyntopoSymmetrize: + BLI_assert_msg(0, "Dynamic topology should've already been handled"); + break; + } } } if (use_multires_undo) { - for (std::unique_ptr &unode : usculpt.nodes) { + LISTBASE_FOREACH (Node *, unode, lb) { if (!STREQ(unode->idname, ob->id.name)) { continue; } @@ -1015,7 +1574,7 @@ static void restore_list(bContext *C, Depsgraph *depsgraph, UndoSculpt &usculpt) } if (subdiv_ccg != nullptr && changed_all_geometry) { - refine_subdiv(depsgraph, ss, ob, subdiv_ccg->subdiv); + sculpt_undo_refine_subdiv(depsgraph, ss, ob, subdiv_ccg->subdiv); } DEG_id_tag_update(&ob->id, ID_RECALC_SHADING); @@ -1075,53 +1634,110 @@ static void restore_list(bContext *C, Depsgraph *depsgraph, UndoSculpt &usculpt) else if (changed_position) { multires_mark_as_modified(depsgraph, ob, MULTIRES_COORDS_MODIFIED); } - } - const bool tag_update = ID_REAL_USERS(ob->data) > 1 || - !BKE_sculptsession_use_pbvh_draw(ob, rv3d) || ss->shapekey_active || - ss->deform_modifiers_active; + const bool tag_update = ID_REAL_USERS(ob->data) > 1 || + !BKE_sculptsession_use_pbvh_draw(ob, rv3d) || ss->shapekey_active || + ss->deform_modifiers_active; - if (tag_update) { - Mesh *mesh = static_cast(ob->data); - if (changed_position) { + if (tag_update) { + Mesh *mesh = static_cast(ob->data); + if (changed_position) { + mesh->tag_positions_changed(); + BKE_sculptsession_free_deformMats(ss); + } + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + } + + if (tag_update) { + Mesh *mesh = static_cast(ob->data); mesh->tag_positions_changed(); + BKE_sculptsession_free_deformMats(ss); } - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + else { + SCULPT_update_object_bounding_box(ob); + } } } -static void free_list(UndoSculpt &usculpt) +static void sculpt_undo_free_list(ListBase *lb) { - for (std::unique_ptr &unode : usculpt.nodes) { - geometry_free_data(&unode->geometry_original); - geometry_free_data(&unode->geometry_modified); - geometry_free_data(&unode->geometry_bmesh_enter); + Node *unode = (Node *)lb->first; + + while (unode != nullptr) { + Node *unode_next = unode->next; + if (unode->bm_entry) { BM_log_entry_drop(unode->bm_entry); + unode->bm_entry = nullptr; + unode->bm_log = nullptr; } + + geometry_free_data(&unode->geometry_original); + geometry_free_data(&unode->geometry_modified); + + MEM_delete(unode); + + unode = unode_next; } - usculpt.nodes.~Vector(); } +/* Most likely we don't need this. */ +#if 0 +static bool sculpt_undo_cleanup(bContext *C, ListBase *lb) +{ + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + Object *ob = BKE_view_layer_active_object_get(view_layer); + Node *unode; + + unode = lb->first; + + if (unode && !STREQ(unode->idname, ob->id.name)) { + if (unode->bm_entry) { + BM_log_cleanup_entry(unode->bm_entry); + } + + return true; + } + + return false; +} +#endif + Node *get_node(PBVHNode *node, Type type) { - UndoSculpt *usculpt = get_nodes(); + UndoSculpt *usculpt = sculpt_undo_get_nodes(); if (usculpt == nullptr) { return nullptr; } - for (std::unique_ptr &unode : usculpt->nodes) { + if (type == Type::None) { + return (Node *)BLI_findptr(&usculpt->nodes, node, offsetof(Node, node)); + } + + LISTBASE_FOREACH (Node *, unode, &usculpt->nodes) { if (unode->node == node && unode->type == type) { - return unode.get(); + return unode; } } return nullptr; } -static size_t alloc_and_store_hidden(SculptSession *ss, Node *unode) +Node *get_first_node() +{ + UndoSculpt *usculpt = sculpt_undo_get_nodes(); + + if (usculpt == nullptr) { + return nullptr; + } + + return static_cast(usculpt->nodes.first); +} + +static size_t sculpt_undo_alloc_and_store_hidden(SculptSession *ss, Node *unode) { PBVHNode *node = static_cast(unode->node); if (!ss->subdiv_ccg) { @@ -1143,18 +1759,18 @@ static size_t alloc_and_store_hidden(SculptSession *ss, Node *unode) /* Allocate node and initialize its default fields specific for the given undo type. * Will also add the node to the list in the undo step. */ -static Node *alloc_node_type(Object *object, Type type) +static Node *sculpt_undo_alloc_node_type(Object *object, Type type) { - UndoSculpt *usculpt = get_nodes(); - std::unique_ptr unode = std::make_unique(); - usculpt->nodes.append(std::move(unode)); - usculpt->undo_size += sizeof(Node); + const size_t alloc_size = sizeof(Node); + Node *unode = MEM_new(__func__); + STRNCPY(unode->idname, object->id.name); + unode->type = type; - Node *node_ptr = usculpt->nodes.last().get(); - STRNCPY(node_ptr->idname, object->id.name); - node_ptr->type = type; + UndoSculpt *usculpt = sculpt_undo_get_nodes(); + BLI_addtail(&usculpt->nodes, unode); + usculpt->undo_size += alloc_size; - return node_ptr; + return unode; } /* Will return first existing undo node of the given type. @@ -1162,48 +1778,41 @@ static Node *alloc_node_type(Object *object, Type type) * return it. */ static Node *find_or_alloc_node_type(Object *object, Type type) { - UndoSculpt *usculpt = get_nodes(); + UndoSculpt *usculpt = sculpt_undo_get_nodes(); - for (std::unique_ptr &unode : usculpt->nodes) { + LISTBASE_FOREACH (Node *, unode, &usculpt->nodes) { if (unode->type == type) { - return unode.get(); + return unode; } } - return alloc_node_type(object, type); + return sculpt_undo_alloc_node_type(object, type); } -static Node *alloc_node(Object *ob, PBVHNode *node, Type type) +static Node *sculpt_undo_alloc_node(Object *ob, PBVHNode *node, Type type) { - UndoSculpt *usculpt = get_nodes(); + UndoSculpt *usculpt = sculpt_undo_get_nodes(); SculptSession *ss = ob->sculpt; + int totvert = 0; + int allvert = 0; + int totgrid = 0; + int maxgrid = 0; + int gridsize = 0; + const int *grids = nullptr; - Node *unode = alloc_node_type(ob, type); + Node *unode = sculpt_undo_alloc_node_type(ob, type); unode->node = node; - int verts_num; - if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { - unode->maxgrid = ss->subdiv_ccg->grids.size(); - unode->gridsize = ss->subdiv_ccg->grid_size; + if (node) { + totvert = BKE_pbvh_node_num_unique_verts(*ss->pbvh, *node); + allvert = BKE_pbvh_node_get_vert_indices(node).size(); + BKE_pbvh_node_get_grids(ss->pbvh, node, &grids, &totgrid, &maxgrid, &gridsize, nullptr); - verts_num = unode->maxgrid * unode->gridsize * unode->gridsize; - - unode->grids = BKE_pbvh_node_get_grid_indices(*node); - usculpt->undo_size += unode->grids.as_span().size_in_bytes(); - } - else { - unode->mesh_verts_num = ss->totvert; - - unode->vert_indices = BKE_pbvh_node_get_vert_indices(node); - unode->unique_verts_num = BKE_pbvh_node_get_unique_vert_indices(node).size(); - - verts_num = unode->vert_indices.size(); - - usculpt->undo_size += unode->vert_indices.as_span().size_in_bytes(); + unode->verts_num = totvert; } bool need_loops = type == Type::Color; - const bool need_faces = ELEM(type, Type::FaceSet, Type::HideFace); + const bool need_faces = type == Type::FaceSet; if (need_loops) { unode->corner_indices = BKE_pbvh_node_get_corner_indices(node); @@ -1223,45 +1832,47 @@ static Node *alloc_node(Object *ob, PBVHNode *node, Type type) } switch (type) { + case Type::None: + BLI_assert_unreachable(); + break; case Type::Position: { - unode->position.reinitialize(verts_num); + unode->position.reinitialize(allvert); usculpt->undo_size += unode->position.as_span().size_in_bytes(); /* Needed for original data lookup. */ - unode->normal.reinitialize(verts_num); + unode->normal.reinitialize(allvert); usculpt->undo_size += unode->normal.as_span().size_in_bytes(); break; } - case Type::HideVert: { - if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { - usculpt->undo_size += alloc_and_store_hidden(ss, unode); - } - else { - unode->vert_hidden.resize(unode->vert_indices.size()); - usculpt->undo_size += unode->vert_hidden.size() / 8; - } - - break; - } case Type::HideFace: { unode->face_hidden.resize(unode->face_indices.size()); usculpt->undo_size += unode->face_hidden.size() / 8; break; } + case Type::HideVert: { + if (maxgrid) { + usculpt->undo_size += sculpt_undo_alloc_and_store_hidden(ss, unode); + } + else { + unode->vert_hidden.resize(allvert); + } + + break; + } case Type::Mask: { - unode->mask.reinitialize(verts_num); + unode->mask.reinitialize(allvert); usculpt->undo_size += unode->mask.as_span().size_in_bytes(); break; } case Type::Color: { /* Allocate vertex colors, even for loop colors we still * need this for original data lookup. */ - unode->col.reinitialize(verts_num); + unode->col.reinitialize(allvert); usculpt->undo_size += unode->col.as_span().size_in_bytes(); /* Allocate loop colors separately too. */ - if (ss->vcol_domain == bke::AttrDomain::Corner) { - unode->loop_col.reinitialize(unode->corner_indices.size()); + if (ss->vcol_domain == AttrDomain::Corner) { + unode->loop_col.reinitialize(unode->corners_num); unode->undo_size += unode->loop_col.as_span().size_in_bytes(); } break; @@ -1280,80 +1891,91 @@ static Node *alloc_node(Object *ob, PBVHNode *node, Type type) } } + if (maxgrid) { + /* Multires. */ + unode->maxgrid = maxgrid; + unode->grids_num = totgrid; + unode->gridsize = gridsize; + + unode->grids.reinitialize(totgrid); + usculpt->undo_size += unode->grids.as_span().size_in_bytes(); + } + else { + /* Regular mesh. */ + unode->mesh_verts_num = ss->totvert; + unode->vert_indices.reinitialize(allvert); + usculpt->undo_size += unode->vert_indices.as_span().size_in_bytes(); + } + if (ss->deform_modifiers_active) { - unode->orig_position.reinitialize(unode->vert_indices.size()); + unode->orig_position.reinitialize(allvert); usculpt->undo_size += unode->orig_position.as_span().size_in_bytes(); } return unode; } -static void store_coords(Object *ob, Node *unode) +static void sculpt_undo_store_coords(Object *ob, Node *unode) { SculptSession *ss = ob->sculpt; + PBVHVertexIter vd; + const PBVHNode *node = static_cast(unode->node); - if (!unode->grids.is_empty()) { - const SubdivCCG &subdiv_ccg = *ss->subdiv_ccg; - const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg); - const Span grids = subdiv_ccg.grids; - { - int index = 0; - for (const int grid : unode->grids) { - CCGElem *elem = grids[grid]; - for (const int i : IndexRange(key.grid_area)) { - unode->position[index] = float3(CCG_elem_offset_co(&key, elem, i)); - index++; - } - } - } - if (key.has_normals) { - int index = 0; - for (const int grid : unode->grids) { - CCGElem *elem = grids[grid]; - for (const int i : IndexRange(key.grid_area)) { - unode->normal[index] = float3(CCG_elem_offset_no(&key, elem, i)); - index++; - } - } - } + int totvert, allvert; + totvert = BKE_pbvh_node_num_unique_verts(*ss->pbvh, *node); + allvert = BKE_pbvh_node_get_vert_indices(node).size(); + + if (ss->deform_modifiers_active && unode->orig_position.is_empty()) { + unode->orig_position.reinitialize(allvert); } - else { - array_utils::gather(BKE_pbvh_get_vert_positions(ss->pbvh).as_span(), - unode->vert_indices.as_span(), - unode->position.as_mutable_span()); - array_utils::gather(BKE_pbvh_get_vert_normals(ss->pbvh), - unode->vert_indices.as_span(), - unode->normal.as_mutable_span()); + + bool have_grids = BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS; + + BKE_pbvh_vertex_iter_begin (ss->pbvh, ((PBVHNode *)unode->node), vd, PBVH_ITER_ALL) { + copy_v3_v3(unode->position[vd.i], vd.co); + if (vd.no) { + copy_v3_v3(unode->normal[vd.i], vd.no); + } + else { + copy_v3_v3(unode->normal[vd.i], vd.fno); + } + if (ss->deform_modifiers_active) { - array_utils::gather(ss->orig_cos.as_span(), - unode->vert_indices.as_span(), - unode->orig_position.as_mutable_span()); + if (!have_grids && ss->shapekey_active) { + float(*cos)[3] = (float(*)[3])ss->shapekey_active->data; + + copy_v3_v3(unode->orig_position[vd.i], cos[vd.index]); + } + else { + copy_v3_v3(unode->orig_position[vd.i], + blender::bke::paint::vertex_attr_ptr(vd.vertex, ss->attrs.orig_co)); + } } } + BKE_pbvh_vertex_iter_end; } -static void store_hidden(Object *ob, Node *unode) +static void sculpt_undo_store_hidden(Object *ob, Node *unode) { - if (!unode->grids.is_empty()) { - /* Already stored during allocation. */ - } + PBVH *pbvh = ob->sculpt->pbvh; + PBVHNode *node = static_cast(unode->node); - const Mesh &mesh = *static_cast(ob->data); - const bke::AttributeAccessor attributes = mesh.attributes(); - const VArraySpan hide_vert = *attributes.lookup(".hide_vert", - bke::AttrDomain::Point); - if (hide_vert.is_empty()) { + const bool *hide_vert = BKE_pbvh_get_vert_hide(pbvh); + if (hide_vert == nullptr) { return; } - PBVHNode *node = static_cast(unode->node); - const Span verts = BKE_pbvh_node_get_vert_indices(node); - for (const int i : verts.index_range()) { - unode->vert_hidden[i].set(hide_vert[verts[i]]); + if (!unode->grids.is_empty()) { + /* Already stored during allocation. */ + } + else { + const blender::Span verts = BKE_pbvh_node_get_vert_indices(node); + for (const int i : verts.index_range()) + unode->vert_hidden[i].set(hide_vert[verts[i]]); } } -static void store_face_hidden(Object &object, Node &unode) +static void sculpt_undo_store_face_hidden(Object &object, Node &unode) { const Mesh &mesh = *static_cast(object.data); const bke::AttributeAccessor attributes = mesh.attributes(); @@ -1368,41 +1990,18 @@ static void store_face_hidden(Object &object, Node &unode) } } -static void store_mask(Object *ob, Node *unode) +static void sculpt_undo_store_mask(Object *ob, Node *unode) { - const SculptSession *ss = ob->sculpt; + SculptSession *ss = ob->sculpt; + PBVHVertexIter vd; - if (!unode->grids.is_empty()) { - const SubdivCCG &subdiv_ccg = *ss->subdiv_ccg; - const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg); - if (key.has_mask) { - const Span grids = subdiv_ccg.grids; - int index = 0; - for (const int grid : unode->grids) { - CCGElem *elem = grids[grid]; - for (const int i : IndexRange(key.grid_area)) { - unode->mask[index] = *CCG_elem_offset_mask(&key, elem, i); - index++; - } - } - } - else { - unode->mask.fill(0.0f); - } - } - else { - const Mesh &mesh = *static_cast(ob->data); - const bke::AttributeAccessor attributes = mesh.attributes(); - if (const VArray mask = *attributes.lookup(".sculpt_mask", bke::AttrDomain::Point)) { - array_utils::gather(mask, unode->vert_indices.as_span(), unode->mask.as_mutable_span()); - } - else { - unode->mask.fill(0.0f); - } + BKE_pbvh_vertex_iter_begin (ss->pbvh, static_cast(unode->node), vd, PBVH_ITER_ALL) { + unode->mask[vd.i] = vd.mask; } + BKE_pbvh_vertex_iter_end; } -static void store_color(Object *ob, Node *unode) +static void sculpt_undo_store_color(Object *ob, Node *unode) { SculptSession *ss = ob->sculpt; @@ -1413,7 +2012,7 @@ static void store_color(Object *ob, Node *unode) BKE_pbvh_store_colors_vertex( ss->pbvh, unode->vert_indices.as_span().take_front(unode->unique_verts_num), unode->col); - if (!unode->loop_col.is_empty() && !unode->corner_indices.is_empty()) { + if (!unode->loop_col.is_empty() && unode->corners_num) { BKE_pbvh_store_colors(ss->pbvh, unode->corner_indices, unode->loop_col); } } @@ -1441,99 +2040,311 @@ static Node *geometry_push(Object *object, Type type) return unode; } -static void store_face_sets(const Mesh &mesh, Node &unode) +static void sculpt_undo_store_face_sets(const Mesh &mesh, Node &unode) { - array_utils::gather( - *mesh.attributes().lookup_or_default(".sculpt_face_set", bke::AttrDomain::Face, 1), + blender::array_utils::gather( + *mesh.attributes().lookup_or_default(".sculpt_face_set", bke::AttrDomain::Face, 0), unode.face_indices.as_span(), unode.face_sets.as_mutable_span()); } -static Node *bmesh_push(Object *ob, PBVHNode *node, Type type) +void ensure_bmlog(Object *ob) { - UndoSculpt *usculpt = get_nodes(); SculptSession *ss = ob->sculpt; + Mesh *me = BKE_object_get_original_mesh(ob); - Node *unode = usculpt->nodes.is_empty() ? nullptr : usculpt->nodes.first().get(); + /* Log exists or object is not in sculpt mode? */ + if (!ss || ss->bm_log) { + return; + } + + /* Try to find log from entries in the undo stack. */ + UndoStack *ustack = ED_undo_stack_get(); + + if (!ustack) { + return; + } + + UndoStep *us = BKE_undosys_stack_active_with_type(ustack, BKE_UNDOSYS_TYPE_SCULPT); + + if (!us) { + // check next step + if (ustack->step_active && ustack->step_active->next && + ustack->step_active->next->type == BKE_UNDOSYS_TYPE_SCULPT) + { + us = ustack->step_active->next; + } + } + + if (!us) { + return; + } + + UndoSculpt *usculpt = sculpt_undosys_step_get_nodes(us); + + if (!ss->bm && (!(me->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) || ss->mode_type != OB_MODE_SCULPT)) { + return; + } + + if (!usculpt) { + // happens during file load + return; + } + + Node *unode = (Node *)usculpt->nodes.first; + + BKE_sculpt_ensure_idmap(ob); + + /*when transition between undo step types the log might simply + have been freed, look for entries to rebuild it with*/ + sculpt_unode_bmlog_ensure(ss, unode); +} + +static Node *sculpt_undo_bmesh_push(Object *ob, PBVHNode *node, Type type) +{ + UndoSculpt *usculpt = sculpt_undo_get_nodes(); + SculptSession *ss = ob->sculpt; + PBVHVertexIter vd; + + Node *unode = static_cast(usculpt->nodes.first); + + ensure_bmlog(ob); + + if (!ss->bm_log) { + ss->bm_log = BM_log_create(ss->bm, ss->bm_idmap); + } + + bool new_node = false; if (unode == nullptr) { - usculpt->nodes.append(std::make_unique()); - unode = usculpt->nodes.last().get(); + new_node = true; + unode = MEM_new(__func__); STRNCPY(unode->idname, ob->id.name); unode->type = type; unode->applied = true; + /* note that every undo type must push a bm_entry for + so we can recreate the BMLog from chained entries + when going to/from other undo system steps */ + if (type == Type::DyntopoEnd) { - unode->bm_entry = BM_log_entry_add(ss->bm_log); - BM_log_before_all_removed(ss->bm, ss->bm_log); + unode->bm_log = ss->bm_log; + unode->bm_entry = BM_log_entry_add(ss->bm, ss->bm_log); + + BM_log_full_mesh(ss->bm, ss->bm_log); } else if (type == Type::DyntopoBegin) { - /* Store a copy of the mesh's current vertices, loops, and - * faces. A full copy like this is needed because entering - * dynamic-topology immediately does topological edits - * (converting faces to triangles) that the BMLog can't - * fully restore from. */ - NodeGeometry *geometry = &unode->geometry_bmesh_enter; - store_geometry_data(geometry, ob); + unode->bm_log = ss->bm_log; + unode->bm_entry = BM_log_entry_add(ss->bm, ss->bm_log); - unode->bm_entry = BM_log_entry_add(ss->bm_log); - BM_log_all_added(ss->bm, ss->bm_log); + BM_log_full_mesh(ss->bm, ss->bm_log); } else { - unode->bm_entry = BM_log_entry_add(ss->bm_log); + unode->bm_log = ss->bm_log; + unode->bm_entry = BM_log_entry_add(ss->bm, ss->bm_log); } + + BLI_addtail(&usculpt->nodes, unode); } if (node) { - const int cd_vert_mask_offset = CustomData_get_offset_named( - &ss->bm->vdata, CD_PROP_FLOAT, ".sculpt_mask"); + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + unode->bm_log = ss->bm_log; + unode->bm_entry = BM_log_entry_check_customdata(ss->bm, ss->bm_log); + } switch (type) { + case Type::HideVert: case Type::Position: case Type::Mask: - /* Before any vertex values get modified, ensure their - * original positions are logged. */ - for (BMVert *vert : BKE_pbvh_bmesh_node_unique_verts(node)) { - BM_log_vert_before_modified(ss->bm_log, vert, cd_vert_mask_offset); - } - for (BMVert *vert : BKE_pbvh_bmesh_node_other_verts(node)) { - BM_log_vert_before_modified(ss->bm_log, vert, cd_vert_mask_offset); + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { + BM_log_vert_modified(ss->bm, ss->bm_log, vd.bm_vert); } + BKE_pbvh_vertex_iter_end; break; - case Type::HideFace: - case Type::HideVert: { - for (BMVert *vert : BKE_pbvh_bmesh_node_unique_verts(node)) { - BM_log_vert_before_modified(ss->bm_log, vert, cd_vert_mask_offset); - } - for (BMVert *vert : BKE_pbvh_bmesh_node_other_verts(node)) { - BM_log_vert_before_modified(ss->bm_log, vert, cd_vert_mask_offset); - } + case Type::HideFace: { + DyntopoSet &faces = BKE_pbvh_bmesh_node_faces(node); - for (BMFace *f : BKE_pbvh_bmesh_node_faces(node)) { - BM_log_face_modified(ss->bm_log, f); + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { + BM_log_vert_modified(ss->bm, ss->bm_log, vd.bm_vert); + } + BKE_pbvh_vertex_iter_end; + + for (BMFace *f : faces) { + BM_log_face_modified(ss->bm, ss->bm_log, f); } break; } + case Type::Color: { + Mesh *mesh = BKE_object_get_original_mesh(ob); + + const CustomDataLayer *color_layer = BKE_id_attribute_search( + &mesh->id, + BKE_id_attributes_active_color_name(&mesh->id), + CD_MASK_COLOR_ALL, + ATTR_DOMAIN_MASK_POINT | ATTR_DOMAIN_MASK_CORNER); + + if (!color_layer) { + break; + } + AttrDomain domain = BKE_id_attribute_domain(&mesh->id, color_layer); + + if (domain == AttrDomain::Point) { + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { + BM_log_vert_modified(ss->bm, ss->bm_log, vd.bm_vert); + } + BKE_pbvh_vertex_iter_end; + } + else if (domain == AttrDomain::Corner) { + DyntopoSet &faces = BKE_pbvh_bmesh_node_faces(node); + + for (BMFace *f : faces) { + BM_log_face_modified(ss->bm, ss->bm_log, f); + } + } + + break; + } + case Type::FaceSet: { + DyntopoSet &faces = BKE_pbvh_bmesh_node_faces(node); + + for (BMFace *f : faces) { + BM_log_face_if_modified(ss->bm, ss->bm_log, f); + } + + break; + } case Type::DyntopoBegin: case Type::DyntopoEnd: case Type::DyntopoSymmetrize: case Type::Geometry: - case Type::FaceSet: - case Type::Color: + case Type::None: break; } } + else { + switch (type) { + case Type::DyntopoSymmetrize: + case Type::Geometry: + BM_log_full_mesh(ss->bm, ss->bm_log); + break; + default: // to avoid warnings + break; + } + } + + if (new_node) { + sculpt_undo_print_nodes(ob, nullptr); + } return unode; } -Node *push_node(Object *ob, PBVHNode *node, Type type) +bool ensure_dyntopo_node_undo( + Object *ob, PBVHNode *node, Type type, Type extraType, Type force_push_mask) { SculptSession *ss = ob->sculpt; + UndoSculpt *usculpt = sculpt_undo_get_nodes(); + + if (!usculpt) { + printf("%s: possible undo error.\n", __func__); + return false; + } + + Node *unode = (Node *)usculpt->nodes.first; + + if (!unode) { + unode = sculpt_undo_alloc_node_type(ob, type); + + BLI_strncpy(unode->idname, ob->id.name, sizeof(unode->idname)); + + unode->type = type; + unode->applied = true; + unode->bm_log = ss->bm_log; + unode->bm_entry = BM_log_entry_add(ss->bm, ss->bm_log); + + return undo::ensure_dyntopo_node_undo(ob, node, type, extraType, force_push_mask); + } + else if (!(unode->typemask & (1 << int(type)))) { + /* Add a log sub-entry. */ + BM_log_entry_add_delta_set(ss->bm, ss->bm_log); + } + + if (!node) { + return false; + } + + int node_id = BKE_pbvh_get_node_id(ss->pbvh, node); + bool pushed = false; + + if (((type & force_push_mask) != Type::None) || unode->dyntopo_undo_set.add({node_id, type})) { + sculpt_undo_bmesh_push(ob, node, type); + pushed = true; + } + + if (extraType != Type::None && (((extraType & force_push_mask) != Type::None) || + unode->dyntopo_undo_set.add({node_id, extraType}))) + { + sculpt_undo_bmesh_push(ob, node, type); + pushed = true; + } + + return pushed; +} + +static bool check_first_undo_entry_dyntopo(Object *ob) +{ + UndoStack *ustack = ED_undo_stack_get(); + if (!ustack || !ob->sculpt || !ob->sculpt->bm) { + return false; + } + + UndoStep *us = ustack->step_init ? ustack->step_init : ustack->step_active; + bool bad = false; + + if (!us) { + bad = true; + } + else if (us->type) { + if (!STREQ(us->type->name, "Sculpt")) { + bad = true; + } + else { + SculptUndoStep *step = (SculptUndoStep *)us; + Node *unode = (Node *)step->data.nodes.first; + + if (!unode) { + bad = true; + } + else { + UndoStep *act = ustack->step_active; + + if (!act->type || !STREQ(act->type->name, "Sculpt")) { + bad = unode->type != Type::DyntopoBegin; + } + } + } + } + else { + bad = true; + } + + if (bad) { + sculpt_undo_push_begin_ex(ob, "Dyntopo Begin", true); + push_node(ob, nullptr, Type::DyntopoBegin); + push_end(ob); + } + + return bad; +} + +Node *push_node(Object *ob, PBVHNode *node, Type type) +{ + SculptSession *ss = ob->sculpt; Node *unode; /* List is manipulated by multiple threads, so we lock. */ @@ -1541,61 +2352,84 @@ Node *push_node(Object *ob, PBVHNode *node, Type type) ss->needs_flush_to_id = 1; - threading::isolate_task([&]() { - if (ss->bm || ELEM(type, Type::DyntopoBegin, Type::DyntopoEnd)) { - /* Dynamic topology stores only one undo node per stroke, - * regardless of the number of PBVH nodes modified. */ - unode = bmesh_push(ob, node, type); - BLI_thread_unlock(LOCK_CUSTOM1); - // return unode; - return; - } - if (type == Type::Geometry) { - unode = geometry_push(ob, type); - BLI_thread_unlock(LOCK_CUSTOM1); - // return unode; - return; - } - if ((unode = get_node(node, type))) { - BLI_thread_unlock(LOCK_CUSTOM1); - // return unode; - return; - } - - unode = alloc_node(ob, node, type); - - /* NOTE: If this ever becomes a bottleneck, make a lock inside of the node. - * so we release global lock sooner, but keep data locked for until it is - * fully initialized. */ - switch (type) { - case Type::Position: - store_coords(ob, unode); - break; - case Type::HideVert: - store_hidden(ob, unode); - break; - case Type::HideFace: - store_face_hidden(*ob, *unode); - break; - case Type::Mask: - store_mask(ob, unode); - break; - case Type::Color: - store_color(ob, unode); - break; - case Type::DyntopoBegin: - case Type::DyntopoEnd: - case Type::DyntopoSymmetrize: - BLI_assert_msg(0, "Dynamic topology should've already been handled"); - case Type::Geometry: - break; - case Type::FaceSet: - store_face_sets(*static_cast(ob->data), *unode); - break; - } - + if (ss->bm || ELEM(type, Type::DyntopoBegin, Type::DyntopoEnd)) { + /* Dynamic topology stores only one undo node per stroke, + * regardless of the number of PBVH nodes modified. */ + unode = sculpt_undo_bmesh_push(ob, node, type); + sculpt_undo_print_nodes(ob, nullptr); BLI_thread_unlock(LOCK_CUSTOM1); - }); + return unode; + } + if (type == Type::Geometry) { + unode = geometry_push(ob, type); + sculpt_undo_print_nodes(ob, nullptr); + BLI_thread_unlock(LOCK_CUSTOM1); + return unode; + } + if ((unode = get_node(node, type))) { + sculpt_undo_print_nodes(ob, nullptr); + BLI_thread_unlock(LOCK_CUSTOM1); + return unode; + } + + unode = sculpt_undo_alloc_node(ob, node, type); + + /* NOTE: If this ever becomes a bottleneck, make a lock inside of the node. + * so we release global lock sooner, but keep data locked for until it is + * fully initialized. + */ + + if (!unode->grids.is_empty()) { + int totgrid; + const int *grids; + BKE_pbvh_node_get_grids(ss->pbvh, node, &grids, &totgrid, nullptr, nullptr, nullptr); + unode->grids.as_mutable_span().copy_from({grids, totgrid}); + } + else { + unode->vert_indices.as_mutable_span().copy_from(BKE_pbvh_node_get_vert_indices(node)); + + if (!unode->corner_indices.is_empty()) { + Span corner_indices = BKE_pbvh_node_get_corner_indices( + static_cast(unode->node)); + + if (!corner_indices.is_empty()) { + unode->corner_indices.as_mutable_span().copy_from(corner_indices); + unode->mesh_corners_num = BKE_object_get_original_mesh(ob)->corners_num; + } + } + } + + switch (type) { + case Type::None: + BLI_assert_unreachable(); + break; + case Type::Position: + sculpt_undo_store_coords(ob, unode); + break; + case Type::HideVert: + sculpt_undo_store_hidden(ob, unode); + break; + case Type::HideFace: + sculpt_undo_store_face_hidden(*ob, *unode); + break; + case Type::Mask: + if (pbvh_has_mask(ss->pbvh)) { + sculpt_undo_store_mask(ob, unode); + } + break; + case Type::Color: + sculpt_undo_store_color(ob, unode); + break; + case Type::DyntopoBegin: + case Type::DyntopoEnd: + case Type::DyntopoSymmetrize: + BLI_assert_msg(0, "Dynamic topology should've already been handled"); + case Type::Geometry: + break; + case Type::FaceSet: + sculpt_undo_store_face_sets(*static_cast(ob->data), *unode); + break; + } /* Store sculpt pivot. */ copy_v3_v3(unode->pivot_pos, ss->pivot_pos); @@ -1609,11 +2443,57 @@ Node *push_node(Object *ob, PBVHNode *node, Type type) unode->shapeName[0] = '\0'; } + sculpt_undo_print_nodes(ob, nullptr); + + BLI_thread_unlock(LOCK_CUSTOM1); + return unode; } +static bool sculpt_attribute_ref_equals(SculptAttrRef *a, SculptAttrRef *b) +{ + return a->domain == b->domain && a->type == b->type && STREQ(a->name, b->name); +} + static void sculpt_save_active_attribute(Object *ob, SculptAttrRef *attr) { + Mesh *me = BKE_object_get_original_mesh(ob); + const CustomDataLayer *layer; + + if (ob && me && (layer = BKE_id_attributes_active_get((ID *)me))) { + attr->domain = BKE_id_attribute_domain((ID *)me, layer); + BLI_strncpy(attr->name, layer->name, sizeof(attr->name)); + attr->type = eCustomDataType(layer->type); + } + else { + attr->domain = NO_ACTIVE_LAYER; + attr->name[0] = 0; + } + + attr->was_set = true; +} + +static void sculpt_save_active_attribute_color(Object *ob, SculptAttrRef *attr) +{ + Mesh *me = BKE_object_get_original_mesh(ob); + const CustomDataLayer *layer; + + if (ob && me && + (layer = BKE_id_attribute_search(&me->id, + BKE_id_attributes_active_color_name(&me->id), + CD_MASK_COLOR_ALL, + ATTR_DOMAIN_MASK_POINT | ATTR_DOMAIN_MASK_CORNER))) + { + attr->domain = BKE_id_attribute_domain((ID *)me, layer); + BLI_strncpy(attr->name, layer->name, sizeof(attr->name)); + attr->type = eCustomDataType(layer->type); + } + else { + attr->domain = NO_ACTIVE_LAYER; + attr->name[0] = 0; + } + + using namespace blender; Mesh *mesh = BKE_object_get_original_mesh(ob); attr->was_set = true; attr->domain = NO_ACTIVE_LAYER; @@ -1637,16 +2517,17 @@ static void sculpt_save_active_attribute(Object *ob, SculptAttrRef *attr) attr->type = meta_data->data_type; } -void push_begin(Object *ob, const wmOperator *op) -{ - push_begin_ex(ob, op->type->name); -} - -void push_begin_ex(Object *ob, const char *name) +static void sculpt_undo_push_begin_ex(Object *ob, const char *name, bool no_first_entry_check) { UndoStack *ustack = ED_undo_stack_get(); + ensure_bmlog(ob); + if (ob != nullptr) { + if (!no_first_entry_check && ob->sculpt && ob->sculpt->bm) { + check_first_undo_entry_dyntopo(ob); + } + /* If possible, we need to tag the object and its geometry data as 'changed in the future' in * the previous undo step if it's a memfile one. */ ED_undosys_stack_memfile_id_changed_tag(ustack, &ob->id); @@ -1660,18 +2541,31 @@ void push_begin_ex(Object *ob, const char *name) ustack, C, name, BKE_UNDOSYS_TYPE_SCULPT); if (!us->active_color_start.was_set) { - sculpt_save_active_attribute(ob, &us->active_color_start); + sculpt_save_active_attribute_color(ob, &us->active_color_start); + } + if (!us->active_attr_start.was_set) { + sculpt_save_active_attribute(ob, &us->active_attr_start); } /* Set end attribute in case push_end is not called, * so we don't end up with corrupted state. */ if (!us->active_color_end.was_set) { - sculpt_save_active_attribute(ob, &us->active_color_end); + sculpt_save_active_attribute_color(ob, &us->active_color_end); us->active_color_end.was_set = false; } } +void push_begin_ex(Object *ob, const char *name) +{ + return sculpt_undo_push_begin_ex(ob, name, false); +} + +void push_begin(Object *ob, const wmOperator *op) +{ + push_begin_ex(ob, op->type->name); +} + void push_end(Object *ob) { push_end_ex(ob, false); @@ -1679,10 +2573,14 @@ void push_end(Object *ob) void push_end_ex(Object *ob, const bool use_nested_undo) { - UndoSculpt *usculpt = get_nodes(); + UndoSculpt *usculpt = sculpt_undo_get_nodes(); /* We don't need normals in the undo stack. */ - for (std::unique_ptr &unode : usculpt->nodes) { + LISTBASE_FOREACH (Node *, unode, &usculpt->nodes) { + if (unode->bm_entry) { + update_unode_bmesh_memsize(unode); + } + usculpt->undo_size -= unode->normal.as_span().size_in_bytes(); unode->normal = {}; } @@ -1702,17 +2600,18 @@ void push_end_ex(Object *ob, const bool use_nested_undo) SculptUndoStep *us = (SculptUndoStep *)BKE_undosys_stack_init_or_active_with_type( ustack, BKE_UNDOSYS_TYPE_SCULPT); - sculpt_save_active_attribute(ob, &us->active_color_end); - print_nodes(ob, nullptr); + sculpt_save_active_attribute(ob, &us->active_attr_end); + sculpt_save_active_attribute_color(ob, &us->active_color_end); + sculpt_undo_print_nodes(ob, nullptr); } /* -------------------------------------------------------------------- */ /** \name Implements ED Undo System * \{ */ -static void set_active_layer(bContext *C, SculptAttrRef *attr) +static void sculpt_undo_set_active_layer(bContext *C, SculptAttrRef *attr, bool is_color) { - if (attr->domain == bke::AttrDomain::Auto) { + if (attr->domain == AttrDomain::Auto) { return; } @@ -1720,7 +2619,12 @@ static void set_active_layer(bContext *C, SculptAttrRef *attr) Mesh *mesh = BKE_object_get_original_mesh(ob); SculptAttrRef existing; - sculpt_save_active_attribute(ob, &existing); + if (is_color) { + sculpt_save_active_attribute_color(ob, &existing); + } + else { + sculpt_save_active_attribute(ob, &existing); + } CustomDataLayer *layer = BKE_id_attribute_find(&mesh->id, attr->name, attr->type, attr->domain); @@ -1757,14 +2661,22 @@ static void set_active_layer(bContext *C, SculptAttrRef *attr) if (ob->sculpt && ob->sculpt->pbvh) { BKE_pbvh_update_active_vcol(ob->sculpt->pbvh, mesh); + + if (!sculpt_attribute_ref_equals(&existing, attr)) { + bke::pbvh::update_vertex_data(*ob->sculpt->pbvh, PBVH_UpdateColor); + } } } + else if (layer) { + BKE_id_attributes_active_set(&mesh->id, layer->name); + } } static void sculpt_undosys_step_encode_init(bContext * /*C*/, UndoStep *us_p) { SculptUndoStep *us = (SculptUndoStep *)us_p; - new (&us->data.nodes) Vector>(); + /* Dummy, memory is cleared anyway. */ + BLI_listbase_clear(&us->data.nodes); } static bool sculpt_undosys_step_encode(bContext * /*C*/, Main *bmain, UndoStep *us_p) @@ -1774,13 +2686,13 @@ static bool sculpt_undosys_step_encode(bContext * /*C*/, Main *bmain, UndoStep * SculptUndoStep *us = (SculptUndoStep *)us_p; us->step.data_size = us->data.undo_size; - Node *unode = us->data.nodes.is_empty() ? nullptr : us->data.nodes.last().get(); + Node *unode = static_cast(us->data.nodes.last); if (unode && unode->type == Type::DyntopoEnd) { us->step.use_memfile_step = true; } us->step.is_applied = true; - if (!us->data.nodes.is_empty()) { + if (!BLI_listbase_is_empty(&us->data.nodes)) { bmain->is_memfile_undo_flush_needed = true; } @@ -1792,11 +2704,10 @@ static void sculpt_undosys_step_decode_undo_impl(bContext *C, SculptUndoStep *us) { BLI_assert(us->step.is_applied == true); - - restore_list(C, depsgraph, us->data); + sculpt_undo_restore_list(C, depsgraph, &us->data.nodes, -1); us->step.is_applied = false; - print_nodes(CTX_data_active_object(C), nullptr); + sculpt_undo_print_nodes(CTX_data_active_object(C), us); } static void sculpt_undosys_step_decode_redo_impl(bContext *C, @@ -1804,11 +2715,10 @@ static void sculpt_undosys_step_decode_redo_impl(bContext *C, SculptUndoStep *us) { BLI_assert(us->step.is_applied == false); - - restore_list(C, depsgraph, us->data); + sculpt_undo_restore_list(C, depsgraph, &us->data.nodes, 1); us->step.is_applied = true; - print_nodes(CTX_data_active_object(C), nullptr); + sculpt_undo_print_nodes(CTX_data_active_object(C), us); } static void sculpt_undosys_step_decode_undo(bContext *C, @@ -1829,12 +2739,18 @@ static void sculpt_undosys_step_decode_undo(bContext *C, while ((us_iter != us) || (!is_final && us_iter == us)) { BLI_assert(us_iter->step.type == us->step.type); /* Previous loop ensures this. */ - set_active_layer(C, &((SculptUndoStep *)us_iter)->active_color_start); + sculpt_undo_set_active_layer(C, &((SculptUndoStep *)us_iter)->active_attr_start, false); + sculpt_undo_set_active_layer(C, &((SculptUndoStep *)us_iter)->active_color_start, true); + sculpt_undosys_step_decode_undo_impl(C, depsgraph, us_iter); + // sculpt_undo_set_active_layer(C, &((SculptUndoStep *)us_iter)->active_attr_start); if (us_iter == us) { if (us_iter->step.prev && us_iter->step.prev->type == BKE_UNDOSYS_TYPE_SCULPT) { - set_active_layer(C, &((SculptUndoStep *)us_iter->step.prev)->active_color_end); + sculpt_undo_set_active_layer( + C, &((SculptUndoStep *)us_iter->step.prev)->active_attr_end, false); + sculpt_undo_set_active_layer( + C, &((SculptUndoStep *)us_iter->step.prev)->active_color_end, true); } break; } @@ -1853,11 +2769,13 @@ static void sculpt_undosys_step_decode_redo(bContext *C, Depsgraph *depsgraph, S us_iter = (SculptUndoStep *)us_iter->step.prev; } while (us_iter && (us_iter->step.is_applied == false)) { - set_active_layer(C, &((SculptUndoStep *)us_iter)->active_color_end); + sculpt_undo_set_active_layer(C, &((SculptUndoStep *)us_iter)->active_attr_start, false); + sculpt_undo_set_active_layer(C, &((SculptUndoStep *)us_iter)->active_color_start, true); sculpt_undosys_step_decode_redo_impl(C, depsgraph, us_iter); if (us_iter == us) { - set_active_layer(C, &((SculptUndoStep *)us_iter)->active_color_start); + sculpt_undo_set_active_layer(C, &((SculptUndoStep *)us_iter)->active_attr_end, false); + sculpt_undo_set_active_layer(C, &((SculptUndoStep *)us_iter)->active_color_end, true); break; } us_iter = (SculptUndoStep *)us_iter->step.next; @@ -1890,11 +2808,7 @@ static void sculpt_undosys_step_decode( * (some) evaluated data. */ BKE_scene_graph_evaluated_ensure(depsgraph, bmain); - Mesh *mesh = static_cast(ob->data); - /* Don't add sculpt topology undo steps when reading back undo state. - * The undo steps must enter/exit for us. */ - mesh->flag &= ~ME_SCULPT_DYNAMIC_TOPOLOGY; - ED_object_sculptmode_enter_ex(bmain, depsgraph, scene, ob, true, nullptr); + ED_object_sculptmode_enter_ex(bmain, depsgraph, scene, ob, true, nullptr, false); } if (ob->sculpt) { @@ -1920,7 +2834,7 @@ static void sculpt_undosys_step_decode( static void sculpt_undosys_step_free(UndoStep *us_p) { SculptUndoStep *us = (SculptUndoStep *)us_p; - free_list(us->data); + sculpt_undo_free_list(&us->data.nodes); } void geometry_begin(Object *ob, const wmOperator *op) @@ -1937,6 +2851,9 @@ void geometry_begin_ex(Object *ob, const char *name) void geometry_end(Object *ob) { + /* Ensure sculpt attribute references are up to date. */ + BKE_sculptsession_update_attr_refs(ob); + push_node(ob, nullptr, Type::Geometry); push_end(ob); } @@ -1967,9 +2884,14 @@ static UndoSculpt *sculpt_undosys_step_get_nodes(UndoStep *us_p) return &us->data; } -static UndoSculpt *get_nodes() +static UndoSculpt *sculpt_undo_get_nodes() { UndoStack *ustack = ED_undo_stack_get(); + + if (!ustack) { // happens during file load + return nullptr; + } + UndoStep *us = BKE_undosys_stack_init_or_active_with_type(ustack, BKE_UNDOSYS_TYPE_SCULPT); return us ? sculpt_undosys_step_get_nodes(us) : nullptr; } @@ -1998,7 +2920,7 @@ static UndoSculpt *get_nodes() * * * \{ */ -static bool use_multires_mesh(bContext *C) +static bool sculpt_undo_use_multires_mesh(bContext *C) { if (BKE_paintmode_get_active_from_context(C) != PaintMode::Sculpt) { return false; @@ -2010,7 +2932,7 @@ static bool use_multires_mesh(bContext *C) return sculpt_session->multires.active; } -static void push_all_grids(Object *object) +static void sculpt_undo_push_all_grids(Object *object) { SculptSession *ss = object->sculpt; @@ -2018,15 +2940,14 @@ static void push_all_grids(Object *object) * happens, for example, when an operation which tagged for geometry update was performed prior * to the current operation without making any stroke in between. * - * Skip pushing nodes based on the following logic: on redo Type::Position will - * ensure PBVH for the new base geometry, which will have same coordinates as if we create PBVH - * here. + * Skip pushing nodes based on the following logic: on redo Type::Position will ensure + * PBVH for the new base geometry, which will have same coordinates as if we create PBVH here. */ if (ss->pbvh == nullptr) { return; } - Vector nodes = bke::pbvh::search_gather(ss->pbvh, {}); + Vector nodes = blender::bke::pbvh::search_gather(ss->pbvh, {}); for (PBVHNode *node : nodes) { Node *unode = push_node(object, node, Type::Position); unode->node = nullptr; @@ -2035,7 +2956,7 @@ static void push_all_grids(Object *object) void push_multires_mesh_begin(bContext *C, const char *str) { - if (!use_multires_mesh(C)) { + if (!sculpt_undo_use_multires_mesh(C)) { return; } @@ -2046,12 +2967,12 @@ void push_multires_mesh_begin(bContext *C, const char *str) Node *geometry_unode = push_node(object, nullptr, Type::Geometry); geometry_unode->geometry_clear_pbvh = false; - push_all_grids(object); + sculpt_undo_push_all_grids(object); } void push_multires_mesh_end(bContext *C, const char *str) { - if (!use_multires_mesh(C)) { + if (!sculpt_undo_use_multires_mesh(C)) { ED_undo_push(C, str); return; } @@ -2066,4 +2987,17 @@ void push_multires_mesh_end(bContext *C, const char *str) /** \} */ +void fast_save_bmesh(Object *ob) +{ + if (!ob->sculpt || !ob->sculpt->bm) { + return; + } + + SculptSession *ss = ob->sculpt; + BMesh *bm = ss->bm; + + struct BMeshToMeshParams params = {}; + params.update_shapekey_indices = true; + BM_mesh_bm_to_me(nullptr, bm, (Mesh *)ob->data, ¶ms); +} } // namespace blender::ed::sculpt_paint::undo diff --git a/source/blender/editors/sculpt_paint/sculpt_uv.cc b/source/blender/editors/sculpt_paint/sculpt_uv.cc index 3476bb2d4c2..e24a72561ad 100644 --- a/source/blender/editors/sculpt_paint/sculpt_uv.cc +++ b/source/blender/editors/sculpt_paint/sculpt_uv.cc @@ -852,6 +852,7 @@ static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wm alpha = BKE_brush_alpha_get(scene, brush); radius = BKE_brush_size_get(scene, brush); + sima = CTX_wm_space_image(C); ED_space_image_get_size(sima, &width, &height); ED_space_image_get_zoom(sima, region, &zoomx, &zoomy); diff --git a/source/blender/editors/space_info/info_stats.cc b/source/blender/editors/space_info/info_stats.cc index 3074cf397c2..c7dd1c5aacf 100644 --- a/source/blender/editors/space_info/info_stats.cc +++ b/source/blender/editors/space_info/info_stats.cc @@ -375,8 +375,10 @@ static void stats_object_sculpt(const Object *ob, SceneStats *stats) stats->totfacesculpt = ss->totfaces; break; case PBVH_BMESH: - stats->totvertsculpt = ob->sculpt->bm->totvert; - stats->tottri = ob->sculpt->bm->totface; + if (ob->sculpt->bm) { + stats->totvertsculpt = ob->sculpt->bm->totvert; + stats->tottri = ob->sculpt->bm->totface; + } break; case PBVH_GRIDS: stats->totvertsculpt = BKE_pbvh_get_grid_num_verts(ss->pbvh); diff --git a/source/blender/editors/space_view3d/CMakeLists.txt b/source/blender/editors/space_view3d/CMakeLists.txt index 52945d2d5c0..440f7319f8f 100644 --- a/source/blender/editors/space_view3d/CMakeLists.txt +++ b/source/blender/editors/space_view3d/CMakeLists.txt @@ -17,6 +17,7 @@ set(INC ../../render ../../windowmanager ../../../../intern/mantaflow/extern + ../../../../intern/atomic # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna diff --git a/source/blender/editors/undo/memfile_undo.cc b/source/blender/editors/undo/memfile_undo.cc index a3fb961e11c..06e0a33ba42 100644 --- a/source/blender/editors/undo/memfile_undo.cc +++ b/source/blender/editors/undo/memfile_undo.cc @@ -54,6 +54,11 @@ struct MemFileUndoStep { MemFileUndoData *data; }; +MemFileUndoData *memfile_get_step_data(MemFileUndoStep *us) +{ + return us->data; +} + static bool memfile_undosys_poll(bContext *C) { /* other poll functions must run first, this is a catch-all. */ diff --git a/source/blender/editors/util/ed_util.cc b/source/blender/editors/util/ed_util.cc index 337594a7191..a20a1dcda70 100644 --- a/source/blender/editors/util/ed_util.cc +++ b/source/blender/editors/util/ed_util.cc @@ -160,7 +160,7 @@ void ED_editors_init(bContext *C) else if (mode & OB_MODE_ALL_SCULPT) { if (obact == ob) { if (mode == OB_MODE_SCULPT) { - ED_object_sculptmode_enter_ex(bmain, depsgraph, scene, ob, true, reports); + ED_object_sculptmode_enter_ex(bmain, depsgraph, scene, ob, false, reports, true); } else if (mode == OB_MODE_VERTEX_PAINT) { ED_object_vpaintmode_enter_ex(bmain, depsgraph, scene, ob); @@ -253,6 +253,14 @@ void ED_editors_exit(Main *bmain, bool do_undo_system) ED_mesh_mirror_topo_table_end(nullptr); } +/* NotForPR: In order to make testing less painful, + * flush the sculpt mesh prior to autosave. Since this comes with a + * performance cost it must be removed prior to the final merge. + */ +namespace blender::ed::sculpt_paint::undo { +void fast_save_bmesh(Object *ob); +} + bool ED_editors_flush_edits_for_object_ex(Main *bmain, Object *ob, bool for_render, @@ -274,15 +282,7 @@ bool ED_editors_flush_edits_for_object_ex(Main *bmain, multires_flush_sculpt_updates(ob); has_edited = true; - if (for_render) { - /* flush changes from dynamic topology sculpt */ - BKE_sculptsession_bm_to_me_for_render(ob); - } - else { - /* Set reorder=false so that saving the file doesn't reorder - * the BMesh's elements */ - BKE_sculptsession_bm_to_me(ob, false); - } + blender::ed::sculpt_paint::undo::fast_save_bmesh(ob); } } else if (ob->mode & OB_MODE_EDIT) { diff --git a/source/blender/makesdna/DNA_brush_defaults.h b/source/blender/makesdna/DNA_brush_defaults.h index 44a647e60e5..53356b01fcc 100644 --- a/source/blender/makesdna/DNA_brush_defaults.h +++ b/source/blender/makesdna/DNA_brush_defaults.h @@ -16,6 +16,19 @@ /** \name Brush Struct * \{ */ +#define _DNA_DEFAULT_DynTopoSettings \ +{\ + .detail_percent = 25.0f,\ + .detail_size = 8.0f,\ + .constant_detail = 16.0f,\ + .flag = DYNTOPO_COLLAPSE|DYNTOPO_SUBDIVIDE|DYNTOPO_CLEANUP,\ + .mode = DYNTOPO_DETAIL_RELATIVE,\ + .inherit = DYNTOPO_INHERIT_BITMASK,\ + .spacing = 35,\ + .radius_scale = 1.0f,\ + .quality = 1.0f,\ +} + #define _DNA_DEFAULT_Brush \ { \ .blend = 0, \ @@ -29,7 +42,13 @@ .size = 35, /* radius of the brush in pixels */ \ .alpha = 1.0f, /* brush strength/intensity probably variable should be renamed? */ \ .autosmooth_factor = 0.0f, \ + .autosmooth_projection = 0.0f,\ + .autosmooth_radius_factor = 1.0f,\ + .autosmooth_spacing = 12,\ .topology_rake_factor = 0.0f, \ + .topology_rake_projection = 1.0f,\ + .topology_rake_radius_factor = 1.0f,\ + .topology_rake_spacing = 12,\ .crease_pinch_factor = 0.5f, \ .normal_radius_factor = 0.5f, \ .wet_paint_radius_factor = 0.5f, \ @@ -60,6 +79,7 @@ .smooth_stroke_radius = 75, \ .smooth_stroke_factor = 0.9f, \ \ + .smear_deform_blend = 0.5f,\ /* Time delay between dots of paint or sculpting when doing airbrush mode. */ \ .rate = 0.1f, \ \ @@ -106,11 +126,14 @@ \ .mtex = _DNA_DEFAULT_MTex, \ .mask_mtex = _DNA_DEFAULT_MTex, \ + .dyntopo = _DNA_DEFAULT_DynTopoSettings,\ + .concave_mask_factor = 0.75f,\ .falloff_shape = 0,\ + .hard_corner_pin = 1.0f,\ + .sharp_angle_limit = 0.38f,\ .tip_scale_x = 1.0f,\ .tip_roundness = 1.0f,\ } - /** \} */ /* clang-format on */ diff --git a/source/blender/makesdna/DNA_brush_enums.h b/source/blender/makesdna/DNA_brush_enums.h index 034562c12dd..2eb392359e6 100644 --- a/source/blender/makesdna/DNA_brush_enums.h +++ b/source/blender/makesdna/DNA_brush_enums.h @@ -284,6 +284,7 @@ typedef enum eBrushBoundaryDeformType { BRUSH_BOUNDARY_DEFORM_GRAB = 3, BRUSH_BOUNDARY_DEFORM_TWIST = 4, BRUSH_BOUNDARY_DEFORM_SMOOTH = 5, + BRUSH_BOUNDARY_DEFORM_CIRCLE = 6, } eBrushBushBoundaryDeformType; typedef enum eBrushBoundaryFalloffType { @@ -424,7 +425,19 @@ typedef enum eBrushFlags2 { BRUSH_CLOTH_USE_COLLISION = (1 << 6), BRUSH_AREA_RADIUS_PRESSURE = (1 << 7), BRUSH_GRAB_SILHOUETTE = (1 << 8), + BRUSH_USE_COLOR_AS_DISPLACEMENT = (1 << 9), + BRUSH_CUSTOM_AUTOSMOOTH_SPACING = (1 << 10), + BRUSH_CUSTOM_TOPOLOGY_RAKE_SPACING = (1 << 11), + BRUSH_TOPOLOGY_RAKE_IGNORE_BRUSH_FALLOFF = (1 << 12), + BRUSH_SMOOTH_USE_AREA_WEIGHT = (1 << 13), + + /*topology rake in dynamic mode*/ + BRUSH_DYNAMIC_RAKE = (1 << 14), + + /* Forces face set boundaries to be treated as hard edges. */ + BRUSH_HARD_EDGE_MODE = (1 << 15), + BRUSH_CURVATURE_RAKE = (1 << 16), } eBrushFlags2; typedef enum { @@ -504,59 +517,6 @@ typedef enum eBrushCurvesSculptTool { CURVES_SCULPT_TOOL_SLIDE = 10, } eBrushCurvesSculptTool; -/** When #BRUSH_ACCUMULATE is used */ -#define SCULPT_TOOL_HAS_ACCUMULATE(t) \ - ELEM(t, \ - SCULPT_TOOL_DRAW, \ - SCULPT_TOOL_DRAW_SHARP, \ - SCULPT_TOOL_SLIDE_RELAX, \ - SCULPT_TOOL_CREASE, \ - SCULPT_TOOL_BLOB, \ - SCULPT_TOOL_INFLATE, \ - SCULPT_TOOL_CLAY, \ - SCULPT_TOOL_CLAY_STRIPS, \ - SCULPT_TOOL_CLAY_THUMB, \ - SCULPT_TOOL_ROTATE, \ - SCULPT_TOOL_SCRAPE, \ - SCULPT_TOOL_FLATTEN) - -#define SCULPT_TOOL_HAS_NORMAL_WEIGHT(t) \ - ELEM(t, SCULPT_TOOL_GRAB, SCULPT_TOOL_SNAKE_HOOK, SCULPT_TOOL_ELASTIC_DEFORM) - -#define SCULPT_TOOL_HAS_RAKE(t) ELEM(t, SCULPT_TOOL_SNAKE_HOOK) - -#define SCULPT_TOOL_HAS_DYNTOPO(t) \ - (ELEM(t, /* These brushes, as currently coded, cannot support dynamic topology */ \ - SCULPT_TOOL_GRAB, \ - SCULPT_TOOL_ROTATE, \ - SCULPT_TOOL_CLOTH, \ - SCULPT_TOOL_THUMB, \ - SCULPT_TOOL_LAYER, \ - SCULPT_TOOL_DISPLACEMENT_ERASER, \ - SCULPT_TOOL_DRAW_SHARP, \ - SCULPT_TOOL_SLIDE_RELAX, \ - SCULPT_TOOL_ELASTIC_DEFORM, \ - SCULPT_TOOL_BOUNDARY, \ - SCULPT_TOOL_POSE, \ - SCULPT_TOOL_DRAW_FACE_SETS, \ - SCULPT_TOOL_PAINT, \ - SCULPT_TOOL_SMEAR, \ -\ - /* These brushes could handle dynamic topology, \ \ - * but user feedback indicates it's better not to */ \ - SCULPT_TOOL_SMOOTH, \ - SCULPT_TOOL_MASK) == 0) - -#define SCULPT_TOOL_HAS_TOPOLOGY_RAKE(t) \ - (ELEM(t, /* These brushes, as currently coded, cannot support topology rake. */ \ - SCULPT_TOOL_GRAB, \ - SCULPT_TOOL_ROTATE, \ - SCULPT_TOOL_THUMB, \ - SCULPT_TOOL_DRAW_SHARP, \ - SCULPT_TOOL_DISPLACEMENT_ERASER, \ - SCULPT_TOOL_SLIDE_RELAX, \ - SCULPT_TOOL_MASK) == 0) - /** #ImagePaintSettings.tool */ typedef enum eBrushImagePaintTool { PAINT_TOOL_DRAW = 0, @@ -648,8 +608,60 @@ typedef enum eBlurKernelType { typedef enum eBrushFalloffShape { PAINT_FALLOFF_SHAPE_SPHERE = 0, PAINT_FALLOFF_SHAPE_TUBE = 1, + PAINT_FALLOFF_NOOP = 2, } eBrushFalloffShape; +// dyntopo flags +// synced with PBVHTopologyUpdateMode +typedef enum eDynTopoFlags { + DYNTOPO_SUBDIVIDE = 1 << 0, + DYNTOPO_COLLAPSE = 1 << 1, + DYNTOPO_DISABLED = 1 << 2, + DYNTOPO_CLEANUP = 1 << 3, + DYNTOPO_LOCAL_COLLAPSE = 1 << 4, + DYNTOPO_LOCAL_SUBDIVIDE = 1 << 5, + DYNTOPO_MAX_FLAGS = 6, +} eDynTopoFlags; +ENUM_OPERATORS(eDynTopoFlags, DYNTOPO_LOCAL_SUBDIVIDE); + +/* Dyntopo inheritance flags. The flags up to DYNTOPO_MAX_FLAGS + * corruspond to eDynTopoFlags + */ +typedef enum eDynTopoInheritFlags { + /* Flags from eDynTopoFlags. */ + DYNTOPO_INHERIT_SUBDIVIDE = 1 << 0, + DYNTOPO_INHERIT_COLLAPSE = 1 << 1, + DYNTOPO_INHERIT_DISABLED = 1 << 2, + DYNTOPO_INHERIT_CLEANUP = 1 << 3, + DYNTOPO_INHERIT_LOCAL_COLLAPSE = 1 << 4, + DYNTOPO_INHERIT_LOCAL_SUBDIVIDE = 1 << 5, + /* End flags from eDynTopoFlags. */ + + DYNTOPO_INHERIT_DETAIL_RANGE = 1 << 11, + DYNTOPO_INHERIT_DETAIL_PERCENT = 1 << 12, + DYNTOPO_INHERIT_MODE = 1 << 13, + DYNTOPO_INHERIT_CONSTANT_DETAIL = 1 << 14, + DYNTOPO_INHERIT_SPACING = 1 << 15, + DYNTOPO_INHERIT_DETAIL_SIZE = 1 << 16, + DYNTOPO_INHERIT_RADIUS_SCALE = 1 << 17, + DYNTOPO_INHERIT_REPEAT = 1 << 18, + DYNTOPO_INHERIT_QUALITY = 1 << 19, + DYNTOPO_INHERIT_MAX_FLAGS = 20, + // make sure to update DYNTOPO_INHERIT_BITMASK when adding flags here +} eDynTopoInheritFlags; +ENUM_OPERATORS(eDynTopoInheritFlags, DYNTOPO_INHERIT_QUALITY); + +// represents all possible inherit flags +#define DYNTOPO_INHERIT_BITMASK ((1 << DYNTOPO_INHERIT_MAX_FLAGS) - 1) + +// dyntopo mode +enum { + DYNTOPO_DETAIL_RELATIVE = 0, + DYNTOPO_DETAIL_MANUAL = 1, + DYNTOPO_DETAIL_BRUSH = 2, + DYNTOPO_DETAIL_CONSTANT = 3 +}; + typedef enum eBrushCurvesSculptFlag { BRUSH_CURVES_SCULPT_FLAG_SCALE_UNIFORM = (1 << 0), BRUSH_CURVES_SCULPT_FLAG_GROW_SHRINK_INVERT = (1 << 1), @@ -665,4 +677,14 @@ typedef enum eBrushCurvesSculptDensityMode { BRUSH_CURVES_SCULPT_DENSITY_MODE_REMOVE = 2, } eBrushCurvesSculptDensityMode; +/* How to correct distortion in smoothing tools.*/ +typedef enum eAttrCorrectMode { + UNDISTORT_NONE = 0, + UNDISTORT_REPROJECT_VERTS = (1 << 0), /* Reproject attributes. */ + UNDISTORT_REPROJECT_CORNERS = (1 << 1), + UNDISTORT_RELAX_UVS = (1 << 2), /* Relax UVs, overrides REPROJECT_CORNERS (for UVs). */ +} eAttrCorrectMode; +ENUM_OPERATORS(eAttrCorrectMode, UNDISTORT_RELAX_UVS); + #define MAX_BRUSH_PIXEL_RADIUS 500 +#define DYNTOPO_DETAIL_RANGE 0.4f diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h index 069ba23103f..33256acb4e0 100644 --- a/source/blender/makesdna/DNA_brush_types.h +++ b/source/blender/makesdna/DNA_brush_types.h @@ -142,6 +142,19 @@ typedef struct BrushGpencilSettings { struct Material *material_alt; } BrushGpencilSettings; +typedef struct DynTopoSettings { + float detail_percent; + float detail_size; + float constant_detail; + short flag, mode; + int inherit; + int spacing; + float radius_scale; + int repeat; + float quality; + int _pad[1]; +} DynTopoSettings; + typedef struct BrushCurvesSculptSettings { /** Number of curves added by the add brush. */ int add_amount; @@ -178,6 +191,10 @@ typedef struct Brush { struct MTex mtex; struct MTex mask_mtex; + /** Pen Input curves */ + struct CurveMapping *pressure_size_curve; + struct CurveMapping *pressure_strength_curve; + struct Brush *toggle_brush; struct ImBuf *icon_imbuf; @@ -293,13 +310,21 @@ typedef struct Brush { char gpencil_weight_tool; /** Active curves sculpt tool (#eBrushCurvesSculptTool). */ char curves_sculpt_tool; - char _pad1[5]; + char _pad1[1]; float autosmooth_factor; + float autosmooth_radius_factor; + float autosmooth_projection; + int autosmooth_spacing; // spacing for BRUSH_CUSTOM_AUTOSMOOTH_SPACING + float boundary_smooth_factor; + float hard_corner_pin; float tilt_strength_factor; float topology_rake_factor; + float topology_rake_radius_factor; + float topology_rake_projection; + int topology_rake_spacing; // spacing for BRUSH_CUSTOM_TOPOLOGY_RAKE_SPACING float crease_pinch_factor; @@ -360,6 +385,10 @@ typedef struct Brush { float cloth_constraint_softbody_strength; + /* array */ + int array_deform_type; + int array_count; + /* smooth */ int smooth_deform_type; float surface_smooth_shape_preservation; @@ -372,9 +401,14 @@ typedef struct Brush { /* smear */ int smear_deform_type; + float smear_deform_blend; + /* slide/relax */ int slide_deform_type; + /* scene_project */ + int scene_project_direction_type; + /* overlay */ int texture_overlay_alpha; int mask_overlay_alpha; @@ -399,7 +433,16 @@ typedef struct Brush { float mask_stencil_pos[2]; float mask_stencil_dimension[2]; + float concave_mask_factor; + + float sharp_angle_limit; + char _pad2[4]; struct BrushGpencilSettings *gpencil_settings; + + DynTopoSettings dyntopo; + + /* new brush engine stuff */ + struct BrushCurvesSculptSettings *curves_sculpt_settings; int automasking_cavity_blur_steps; diff --git a/source/blender/makesdna/DNA_customdata_types.h b/source/blender/makesdna/DNA_customdata_types.h index d45abd3bcc6..50089f078b5 100644 --- a/source/blender/makesdna/DNA_customdata_types.h +++ b/source/blender/makesdna/DNA_customdata_types.h @@ -52,6 +52,7 @@ typedef struct CustomDataLayer { * attribute was created. */ const AnonymousAttributeIDHandle *anonymous_id; + /** * Run-time data that allows sharing `data` with other entities (mostly custom data layers on * other geometries). @@ -88,6 +89,7 @@ typedef struct CustomData { * Correct size of #CD_NUMTYPES is ensured by CustomData_update_typemap. */ int typemap[53]; + /** Number of layers, size of layers array. */ int totlayer, maxlayer; /** In editmode, total size of all data layers. */ @@ -261,15 +263,18 @@ enum { CD_FLAG_NOCOPY = (1 << 0), CD_FLAG_UNUSED = (1 << 1), /* Indicates the layer is only temporary, also implies no copy */ - CD_FLAG_TEMPORARY = ((1 << 2) | CD_FLAG_NOCOPY), + CD_FLAG_TEMPORARY = ((1 << 2)), // CD_FLAG_TEMPORARY no longer implies CD_FLAG_NOCOPY, this + // wasn't enforced for bmesh /* Indicates the layer is stored in an external file */ CD_FLAG_EXTERNAL = (1 << 3), /* Indicates external data is read into memory */ CD_FLAG_IN_MEMORY = (1 << 4), #ifdef DNA_DEPRECATED_ALLOW CD_FLAG_COLOR_ACTIVE = (1 << 5), - CD_FLAG_COLOR_RENDER = (1 << 6) + CD_FLAG_COLOR_RENDER = (1 << 6), #endif + CD_FLAG_ELEM_NOCOPY = (1 << 8), // disables CustomData_bmesh_copy_data. + CD_FLAG_ELEM_NOINTERP = (1 << 9), }; /* Limits */ diff --git a/source/blender/makesdna/DNA_meshdata_types.h b/source/blender/makesdna/DNA_meshdata_types.h index 014e4ad4393..c8cd5d8562c 100644 --- a/source/blender/makesdna/DNA_meshdata_types.h +++ b/source/blender/makesdna/DNA_meshdata_types.h @@ -9,6 +9,7 @@ #pragma once #include "BLI_sys_types.h" +#include "BLI_utildefines.h" /* -------------------------------------------------------------------- */ /** \name Ordered Selection Storage @@ -438,3 +439,12 @@ typedef struct MRecast { #endif /** \} */ + +typedef enum eSculptFlag { + SCULPTFLAG_VERT_FSET_HIDDEN = (1 << 1), + SCULPTFLAG_NEED_TRIANGULATE = (1 << 2), + SCULPTFLAG_NEED_VALENCE = (1 << 3), + SCULPTFLAG_SPLIT_TEMP = (1 << 4), + SCULPTFLAG_PBVH_BOUNDARY = (1 << 5), +} eSculptFlag; +ENUM_OPERATORS(eSculptFlag, SCULPTFLAG_PBVH_BOUNDARY); diff --git a/source/blender/makesdna/DNA_scene_defaults.h b/source/blender/makesdna/DNA_scene_defaults.h index 8cdd1d5098a..9444ff8c5ff 100644 --- a/source/blender/makesdna/DNA_scene_defaults.h +++ b/source/blender/makesdna/DNA_scene_defaults.h @@ -8,6 +8,9 @@ #pragma once +#include "DNA_brush_defaults.h" +#include "DNA_brush_enums.h" +#include "DNA_scene_enums.h" #include "DNA_view3d_defaults.h" /* clang-format off */ @@ -311,7 +314,11 @@ .unprojected_radius = 0.29, \ .alpha = 0.5f, \ .weight = 0.5f, \ - .flag = UNIFIED_PAINT_SIZE | UNIFIED_PAINT_ALPHA, \ + .flag = UNIFIED_PAINT_SIZE | UNIFIED_PAINT_ALPHA | UNIFIED_PAINT_FLAG_HARD_EDGE_MODE | UNIFIED_PAINT_HARD_CORNER_PIN | UNIFIED_PAINT_FLAG_SHARP_ANGLE_LIMIT, \ + .hard_corner_pin = 1.0f,\ + .sharp_angle_limit = 0.6108f,\ + .smooth_boundary_flag = SCULPT_BOUNDARY_MESH|SCULPT_BOUNDARY_FACE_SET|SCULPT_BOUNDARY_SEAM|SCULPT_BOUNDARY_SHARP_MARK|SCULPT_BOUNDARY_UV,\ + .distort_correction_mode = UNDISTORT_REPROJECT_VERTS|UNDISTORT_REPROJECT_CORNERS|UNDISTORT_RELAX_UVS,\ } #define _DNA_DEFAULTS_ParticleEditSettings \ @@ -415,15 +422,12 @@ #define _DNA_DEFAULT_Sculpt \ { \ - .detail_size = 12,\ - .detail_percent = 25,\ - .constant_detail = 3.0f,\ - .automasking_start_normal_limit = 0.34906585f, /* 20 / 180 * pi. */ \ + .automasking_start_normal_limit = 0.34906585f, /* 20 / 180 * pi */ \ .automasking_start_normal_falloff = 0.25f, \ - .automasking_view_normal_limit = 1.570796, /* 0.5 * pi. */ \ + .automasking_view_normal_limit = 1.570796, /* 0.5 * pi */ \ .automasking_view_normal_falloff = 0.25f, \ - .automasking_boundary_edges_propagation_steps = 1, \ - .flags = SCULPT_DYNTOPO_SUBDIVIDE | SCULPT_DYNTOPO_COLLAPSE,\ + .flags = SCULPT_DYNTOPO_ENABLED,\ + .dyntopo = _DNA_DEFAULT_DynTopoSettings,\ .paint = {\ .symmetry_flags = PAINT_SYMMETRY_FEATHER,\ .tile_offset = {1.0f, 1.0f, 1.0f},\ diff --git a/source/blender/makesdna/DNA_scene_enums.h b/source/blender/makesdna/DNA_scene_enums.h index 8ea21e656e4..1c36f78d8b7 100644 --- a/source/blender/makesdna/DNA_scene_enums.h +++ b/source/blender/makesdna/DNA_scene_enums.h @@ -8,6 +8,8 @@ #pragma once +#include "BLI_utildefines.h" + /** #ToolSettings.vgroupsubset */ typedef enum eVGroupSelect { WT_VGROUP_ALL = 0, @@ -23,3 +25,34 @@ typedef enum eSeqImageFitMethod { SEQ_STRETCH_TO_FILL, SEQ_USE_ORIGINAL_SIZE, } eSeqImageFitMethod; + +typedef enum eSculptBoundary { + SCULPT_BOUNDARY_NONE = 0, + SCULPT_BOUNDARY_MESH = 1 << 0, + SCULPT_BOUNDARY_FACE_SET = 1 << 1, + SCULPT_BOUNDARY_SEAM = 1 << 2, + SCULPT_BOUNDARY_SHARP_MARK = 1 << 3, /* Edges marked as sharp. */ + SCULPT_BOUNDARY_SHARP_ANGLE = 1 << 4, /* Edges whose face angle is above a limit */ + SCULPT_BOUNDARY_UV = 1 << 5, + SCULPT_BOUNDARY_NEEDS_UPDATE = 1 << 6, + SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE = 1 << 7, + SCULPT_BOUNDARY_UPDATE_UV = 1 << 8, + + SCULPT_BOUNDARY_ALL = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5), + SCULPT_BOUNDARY_DEFAULT = (1 << 0) | (1 << 3) | (1 << 4) // mesh and sharp +} eSculptBoundary; +ENUM_OPERATORS(eSculptBoundary, SCULPT_BOUNDARY_UPDATE_SHARP_ANGLE); + +/* Note: This is stored in a single attribute with boundary flags */ +typedef enum eSculptCorner { + SCULPT_CORNER_NONE = 0, + SCULPT_CORNER_BIT_SHIFT = + 16, /* Shift boundary flags by this much to get matching corner flags. */ + SCULPT_CORNER_MESH = 1 << 16, + SCULPT_CORNER_FACE_SET = 1 << 17, + SCULPT_CORNER_SEAM = 1 << 18, + SCULPT_CORNER_SHARP_MARK = 1 << 19, + SCULPT_CORNER_SHARP_ANGLE = 1 << 20, + SCULPT_CORNER_UV = 1 << 21, +} eSculptCorner; +ENUM_OPERATORS(eSculptCorner, SCULPT_CORNER_UV); diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 631d6c9da98..97f7c225951 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -20,6 +20,7 @@ #define USE_SETSCENE_CHECK #include "DNA_ID.h" +#include "DNA_brush_types.h" /* DynTopoSettings */ #include "DNA_color_types.h" /* color management */ #include "DNA_customdata_types.h" /* Scene's runtime custom-data masks. */ #include "DNA_layer_types.h" @@ -1095,6 +1096,7 @@ typedef struct ParticleEditSettings { /** \name Sculpt * \{ */ +/* ------------------------------------------- */ /** Sculpt. */ typedef struct Sculpt { Paint paint; @@ -1113,7 +1115,7 @@ typedef struct Sculpt { int radial_symm[3]; /** Maximum edge length for dynamic topology sculpting (in pixels). */ - float detail_size; + float detail_size DNA_DEPRECATED; /** Direction used for `SCULPT_OT_symmetrize` operator. */ int symmetrize_direction; @@ -1123,8 +1125,8 @@ typedef struct Sculpt { /* Scale for constant detail size. */ /** Constant detail resolution (Blender unit / constant_detail). */ - float constant_detail; - float detail_percent; + float constant_detail DNA_DEPRECATED; + float detail_percent DNA_DEPRECATED; int automasking_boundary_edges_propagation_steps; int automasking_cavity_blur_steps; @@ -1137,6 +1139,8 @@ typedef struct Sculpt { /** For use by operators. */ struct CurveMapping *automasking_cavity_curve_op; struct Object *gravity_object; + + DynTopoSettings dyntopo; } Sculpt; typedef struct CurvesSculpt { @@ -1413,6 +1417,15 @@ typedef struct UnifiedPaintSettings { */ float pixel_radius; float initial_pixel_radius; + + float hard_corner_pin; + float sharp_angle_limit; + char _pad2[2]; + + char distort_correction_mode; /* eAttrCorrectMode bit mask. */ + char hard_edge_mode DNA_DEPRECATED; + int smooth_boundary_flag; + float start_pixel_radius; /** Drawing pressure. */ @@ -1432,15 +1445,17 @@ typedef struct UnifiedPaintSettings { typedef enum { UNIFIED_PAINT_SIZE = (1 << 0), UNIFIED_PAINT_ALPHA = (1 << 1), - UNIFIED_PAINT_WEIGHT = (1 << 5), - UNIFIED_PAINT_COLOR = (1 << 6), - UNIFIED_PAINT_INPUT_SAMPLES = (1 << 7), /** Only used if unified size is enabled, mirrors the brush flag #BRUSH_LOCK_SIZE. */ UNIFIED_PAINT_BRUSH_LOCK_SIZE = (1 << 2), UNIFIED_PAINT_FLAG_UNUSED_0 = (1 << 3), - UNIFIED_PAINT_FLAG_UNUSED_1 = (1 << 4), + UNIFIED_PAINT_WEIGHT = (1 << 5), + UNIFIED_PAINT_COLOR = (1 << 6), + UNIFIED_PAINT_INPUT_SAMPLES = (1 << 7), + UNIFIED_PAINT_HARD_CORNER_PIN = (1 << 10), + UNIFIED_PAINT_FLAG_HARD_EDGE_MODE = (1 << 11), + UNIFIED_PAINT_FLAG_SHARP_ANGLE_LIMIT = (1 << 12), } eUnifiedPaintSettingsFlags; typedef struct CurvePaintSettings { @@ -1731,7 +1746,11 @@ typedef struct ToolSettings { /** Normal Editing. */ float normal_vector[3]; - char _pad6[4]; + + /* NotForPR: Show original coordinates from start of sculpt stroke.*/ + char show_origco; + + char _pad6[3]; /** * Custom Curve Profile for bevel tool: @@ -2577,6 +2596,7 @@ typedef enum ePaintFlags { PAINT_SHOW_BRUSH_ON_SURFACE = (1 << 2), PAINT_USE_CAVITY_MASK = (1 << 3), PAINT_SCULPT_DELAY_UPDATES = (1 << 4), + PAINT_SCULPT_SHOW_PIVOT = (1 << 5), } ePaintFlags; /** @@ -2624,18 +2644,27 @@ typedef enum eSculptFlags { // SCULPT_SHOW_DIFFUSE = (1 << 9), /* deprecated */ /** If set, the mesh will be drawn with smooth-shading in dynamic-topology mode. */ - SCULPT_FLAG_UNUSED_8 = (1 << 10), /* deprecated */ + SCULPT_FLAG_UNUSED_10 = (1 << 10), /* deprecated */ /** If set, dynamic-topology brushes will subdivide short edges. */ - SCULPT_DYNTOPO_SUBDIVIDE = (1 << 12), + SCULPT_DYNTOPO_SUBDIVIDE = (1 << 12), /* deprecated. */ /** If set, dynamic-topology brushes will collapse short edges. */ - SCULPT_DYNTOPO_COLLAPSE = (1 << 11), + SCULPT_DYNTOPO_COLLAPSE = (1 << 11), /* deprecated. */ /** If set, dynamic-topology detail size will be constant in object space. */ - SCULPT_DYNTOPO_DETAIL_CONSTANT = (1 << 13), - SCULPT_DYNTOPO_DETAIL_BRUSH = (1 << 14), - /* unused = (1 << 15), */ - SCULPT_DYNTOPO_DETAIL_MANUAL = (1 << 16), + SCULPT_DYNTOPO_DETAIL_CONSTANT = (1 << 13), /* deprecated. */ + SCULPT_DYNTOPO_DETAIL_BRUSH = (1 << 14), /* deprecated. */ + /* Don't display mask in viewport, but still use it for strokes. */ + SCULPT_HIDE_MASK = (1 << 15), + SCULPT_DYNTOPO_DETAIL_MANUAL = (1 << 16), /* deprecated. */ + + /* Don't display face sets in viewport. */ + SCULPT_HIDE_FACE_SETS = (1 << 17), + SCULPT_FLAG_UNUSED_8 = (1 << 18), + + /* Hides facesets/masks and forces indexed mode to save GPU bandwidth. */ + SCULPT_FLAG_UNUSED_20 = (1 << 20), + SCULPT_DYNTOPO_ENABLED = (1 << 21), } eSculptFlags; /** #Sculpt::transform_mode */ diff --git a/source/blender/makesdna/DNA_view3d_defaults.h b/source/blender/makesdna/DNA_view3d_defaults.h index 1d97cd75d9b..ec5c3097f59 100644 --- a/source/blender/makesdna/DNA_view3d_defaults.h +++ b/source/blender/makesdna/DNA_view3d_defaults.h @@ -52,6 +52,7 @@ * we typically want to see shading too. */ \ .sculpt_mode_mask_opacity = 0.75f, \ .sculpt_mode_face_sets_opacity = 1.0f, \ + .sculpt_mode_face_sets_moire_scale = 0.45f,\ \ .edit_flag = V3D_OVERLAY_EDIT_FACES | V3D_OVERLAY_EDIT_SEAMS | \ V3D_OVERLAY_EDIT_SHARP | V3D_OVERLAY_EDIT_FREESTYLE_EDGE | \ diff --git a/source/blender/makesdna/DNA_view3d_types.h b/source/blender/makesdna/DNA_view3d_types.h index 4dd7e16cb11..ed000329eb8 100644 --- a/source/blender/makesdna/DNA_view3d_types.h +++ b/source/blender/makesdna/DNA_view3d_types.h @@ -205,6 +205,11 @@ typedef struct View3DOverlay { float weight_paint_mode_opacity; float sculpt_mode_mask_opacity; float sculpt_mode_face_sets_opacity; + float sculpt_mode_face_sets_moire_seed; + float sculpt_mode_face_sets_moire_scale; + + /** Sculpt mode settings*/ + int sculpt_flag; float viewer_attribute_opacity; /** Armature edit/pose mode settings. */ @@ -231,6 +236,8 @@ typedef struct View3DOverlay { /** Curves sculpt mode settings. */ float sculpt_curves_cage_opacity; + + char _pad[4]; } View3DOverlay; /** #View3DOverlay.handle_display */ diff --git a/source/blender/makesdna/intern/dna_defaults.c b/source/blender/makesdna/intern/dna_defaults.c index f5a65bd5c63..fbca1cebba4 100644 --- a/source/blender/makesdna/intern/dna_defaults.c +++ b/source/blender/makesdna/intern/dna_defaults.c @@ -156,6 +156,7 @@ SDNA_DEFAULT_DECL_STRUCT(AssetLibraryReference); SDNA_DEFAULT_DECL_STRUCT(bArmature); /* DNA_brush_defaults.h */ +SDNA_DEFAULT_DECL_STRUCT(DynTopoSettings); SDNA_DEFAULT_DECL_STRUCT(Brush); /* DNA_cachefile_defaults.h */ @@ -400,6 +401,7 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = { /* DNA_brush_defaults.h */ SDNA_DEFAULT_DECL(Brush), + SDNA_DEFAULT_DECL(DynTopoSettings), /* DNA_cachefile_defaults.h */ SDNA_DEFAULT_DECL(CacheFile), diff --git a/source/blender/makesrna/intern/rna_brush.cc b/source/blender/makesrna/intern/rna_brush.cc index 9e47b08d27b..9832619cdf5 100644 --- a/source/blender/makesrna/intern/rna_brush.cc +++ b/source/blender/makesrna/intern/rna_brush.cc @@ -6,7 +6,10 @@ * \ingroup RNA */ +#include +#include #include +#include #include "DNA_brush_types.h" #include "DNA_gpencil_legacy_types.h" @@ -17,6 +20,7 @@ #include "DNA_workspace_types.h" #include "BKE_layer.hh" +#include "BKE_sculpt.h" #include "BLI_math_base.h" #include "BLI_string_utf8_symbols.h" @@ -444,6 +448,37 @@ static EnumPropertyItem rna_enum_gpencil_brush_vertex_icons_items[] = { {GP_BRUSH_ICON_VERTEX_REPLACE, "REPLACE", ICON_BRUSH_MIX, "Replace", ""}, {0, nullptr, 0, nullptr, nullptr}, }; + +static EnumPropertyItem rna_enum_brush_dyntopo_mode[] = { + {DYNTOPO_DETAIL_RELATIVE, "RELATIVE", ICON_NONE, "Relative", ""}, + {DYNTOPO_DETAIL_CONSTANT, "CONSTANT", ICON_NONE, "Constant", ""}, + {DYNTOPO_DETAIL_MANUAL, "MANUAL", ICON_NONE, "Manual", ""}, + {DYNTOPO_DETAIL_BRUSH, "BRUSH", ICON_NONE, "Brush", ""}, + {0, nullptr, 0, nullptr, nullptr}, +}; + +static EnumPropertyItem rna_enum_brush_dyntopo_inherit[] = { + {DYNTOPO_SUBDIVIDE, "SUBDIVIDE", ICON_NONE, "Subdivide", ""}, + {DYNTOPO_COLLAPSE, "COLLAPSE", ICON_NONE, "Collapse", ""}, + {DYNTOPO_CLEANUP, "CLEANUP", ICON_NONE, "Cleanup", ""}, + {DYNTOPO_LOCAL_COLLAPSE, "LOCAL_COLLAPSE", ICON_NONE, "Local Collapse", ""}, + {DYNTOPO_LOCAL_SUBDIVIDE, "LOCAL_SUBDIVIDE", ICON_NONE, "Local Subdivide", ""}, + {DYNTOPO_DISABLED, "DISABLED", ICON_NONE, "Disable", ""}, + {DYNTOPO_INHERIT_DETAIL_PERCENT, "DETAIL_PERCENT", ICON_NONE, "Percent", ""}, + {DYNTOPO_INHERIT_MODE, "MODE", ICON_NONE, "Mode", ""}, + {DYNTOPO_INHERIT_CONSTANT_DETAIL, "CONSTANT_DETAIL", ICON_NONE, "Constant Detail", ""}, + {DYNTOPO_INHERIT_SPACING, "SPACING", ICON_NONE, "Spacing", ""}, + {DYNTOPO_INHERIT_DETAIL_SIZE, "DETAIL_SIZE", ICON_NONE, "Detail Size", ""}, + {DYNTOPO_INHERIT_RADIUS_SCALE, "RADIUS_SCALE", ICON_NONE, "Radius Scale", ""}, + {DYNTOPO_INHERIT_REPEAT, + "REPEAT", + ICON_NONE, + "Repeat", + "How many extra times to run the dyntopo remesher."}, + {DYNTOPO_INHERIT_QUALITY, "QUALITY", ICON_NONE, "Quality", ""}, + {0, nullptr, 0, nullptr, nullptr}, +}; + #endif #ifdef RNA_RUNTIME @@ -775,6 +810,8 @@ static void rna_Brush_update(Main * /*bmain*/, Scene * /*scene*/, PointerRNA *pt // WM_main_add_notifier(NC_SPACE | ND_SPACE_VIEW3D, nullptr); } +static void rna_Brush_dyntopo_update(Main * /*bmain*/, Scene * /*scene*/, PointerRNA *ptr) {} + static void rna_Brush_material_update(bContext * /*C*/, PointerRNA * /*ptr*/) { /* number of material users changed */ @@ -863,6 +900,7 @@ static void rna_Brush_set_size(PointerRNA *ptr, int value) /* scale unprojected radius so it stays consistent with brush size */ BKE_brush_scale_unprojected_radius(&brush->unprojected_radius, value, brush->size); + brush->size = value; } @@ -1241,6 +1279,32 @@ static std::optional rna_BrushCurvesSculptSettings_path(const Point return "curves_sculpt_settings"; } +static int rna_DynTopoSettings_inherit_get(PointerRNA *ptr) +{ + Brush *brush; + + if (GS(ptr->owner_id->name) == ID_BR) { + brush = (Brush *)ptr->owner_id; + } + else if (GS(ptr->owner_id->name) == ID_SCE) { + /* Try and fetch the active brush. */ + Scene *scene = (Scene *)ptr->owner_id; + if (scene->toolsettings && scene->toolsettings->sculpt) { + brush = scene->toolsettings->sculpt->paint.brush; + } + } + else { + BLI_assert_unreachable(); + return 0; + } + + if (!brush) { + /* No brush, scene defines all settings. */ + return DYNTOPO_INHERIT_BITMASK; + } + + return BKE_brush_dyntopo_inherit_flags(brush); +} #else static void rna_def_brush_texture_slot(BlenderRNA *brna) @@ -1312,6 +1376,151 @@ static void rna_def_brush_texture_slot(BlenderRNA *brna) TEXTURE_CAPABILITY(has_texture_angle, "Has Texture Angle Source"); } +static void rna_def_dyntopo_settings(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "DynTopoSettings", nullptr); + RNA_def_struct_sdna(srna, "DynTopoSettings"); + RNA_def_struct_ui_text(srna, "Dyntopo Settings", ""); + + prop = RNA_def_property(srna, "spacing", PROP_INT, PROP_PERCENTAGE); + RNA_def_property_int_sdna(prop, nullptr, "spacing"); + RNA_def_property_range(prop, 0, 1000); + RNA_def_property_ui_range(prop, 0, 500, 5, -1); + RNA_def_property_ui_text(prop, + "Spacing", + "Spacing between DynTopo daubs as a percentage of brush diameter; if " + "zero will use brush spacing"); + RNA_def_property_update(prop, 0, "rna_Brush_dyntopo_update"); + + prop = RNA_def_property(srna, "detail_percent", PROP_FLOAT, PROP_PERCENTAGE); + RNA_def_property_float_sdna(prop, nullptr, "detail_percent"); + RNA_def_property_range(prop, 1, 1000); + RNA_def_property_ui_range(prop, 1, 500, 5, -1); + RNA_def_property_ui_text(prop, "Detail Percent", ""); + RNA_def_property_update(prop, 0, "rna_Brush_dyntopo_update"); + + prop = RNA_def_property(srna, "detail_size", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, nullptr, "detail_size"); + RNA_def_property_range(prop, 0.0, 100.0); + RNA_def_property_ui_range(prop, 0.0, 50.0, 0.1, 4); + RNA_def_property_ui_text(prop, "Detail Size", ""); + RNA_def_property_update(prop, 0, "rna_Brush_dyntopo_update"); + + prop = RNA_def_property(srna, "constant_detail", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, nullptr, "constant_detail"); + RNA_def_property_range(prop, 0.0001, FLT_MAX); + RNA_def_property_ui_range(prop, 0.001, 1000.0, 10, 2); + RNA_def_property_ui_text(prop, + "Resolution", + "Maximum edge length for dynamic topology sculpting (as divisor " + "of blender unit - higher value means smaller edge length)"); + RNA_def_property_update(prop, 0, "rna_Brush_dyntopo_update"); + + prop = RNA_def_property(srna, "subdivide", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag", DYNTOPO_SUBDIVIDE); + RNA_def_property_ui_icon(prop, ICON_NONE, 0); + RNA_def_property_ui_text(prop, "Subdivide", "Enable Dyntopo Subdivision"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, 0, "rna_Brush_dyntopo_update"); + + prop = RNA_def_property(srna, "disabled", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag", DYNTOPO_DISABLED); + RNA_def_property_ui_icon(prop, ICON_NONE, 0); + RNA_def_property_ui_text(prop, "No Dyntopo", "Disable Dyntopo for this brush"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, 0, "rna_Brush_dyntopo_update"); + + prop = RNA_def_property(srna, "cleanup", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag", DYNTOPO_CLEANUP); + RNA_def_property_ui_icon(prop, ICON_NONE, 0); + RNA_def_property_ui_text(prop, "Cleanup", "Dissolve Verts With Only 3 or 4 faces"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, 0, "rna_Brush_dyntopo_update"); + + prop = RNA_def_property(srna, "collapse", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag", DYNTOPO_COLLAPSE); + RNA_def_property_ui_icon(prop, ICON_NONE, 0); + RNA_def_property_ui_text(prop, "Collapse", "Enable Dyntopo Decimation"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, 0, "rna_Brush_dyntopo_update"); + + prop = RNA_def_property(srna, "local_collapse", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag", DYNTOPO_LOCAL_COLLAPSE); + RNA_def_property_ui_icon(prop, ICON_NONE, 0); + RNA_def_property_ui_text( + prop, + "Local Collapse", + "When collapse is disabled, collapse anyway based on local edge lengths under brush"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, 0, "rna_Brush_dyntopo_update"); + + prop = RNA_def_property(srna, "local_subdivide", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag", DYNTOPO_LOCAL_SUBDIVIDE); + RNA_def_property_ui_icon(prop, ICON_NONE, 0); + RNA_def_property_ui_text( + prop, + "Local Subdivide", + "When subdivide is disabled, subdivide anyway based on local edge lengths under brush"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, 0, "rna_Brush_dyntopo_update"); + + prop = RNA_def_property(srna, "mode", PROP_ENUM, 0); + RNA_def_property_enum_sdna(prop, nullptr, "mode"); + RNA_def_property_enum_items(prop, rna_enum_brush_dyntopo_mode); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_ui_text(prop, "Mode", "Detail Mode"); + + /* Auto-generate is_XXX_overridden members. */ + EnumPropertyItem *item = rna_enum_brush_dyntopo_inherit; + while (item->identifier) { + char identifier[128]; + char name[128]; + + /* Makesrna does not compile with BLI_snprintf. */ + sprintf(identifier, "is_%s_overridden", item->identifier); + sprintf(name, "Overridden"); + + /* Makesrna does not compile with BLI_str_tolower_ascii. */ + char *c = identifier; + while (*c) { + *c = tolower(*c); + c++; + } + + /* Use strdup to avoid memory leak warnings; identifier and name are global constants. */ + prop = RNA_def_property(srna, strdup(identifier), PROP_BOOLEAN, 0); + RNA_def_property_boolean_negative_sdna(prop, nullptr, "inherit", item->value); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE | PROP_EDITABLE); /* Read-only. */ + RNA_def_property_ui_text(prop, strdup(name), "Brush overrides this setting."); + + item++; + } + + prop = RNA_def_property(srna, "radius_scale", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, nullptr, "radius_scale"); + RNA_def_property_range(prop, 0.0f, 15.0f); + RNA_def_property_ui_range(prop, 0.0f, 2.0f, 0.001, 4); + RNA_def_property_ui_text(prop, "Scale dyntopo radius", ""); + RNA_def_property_update(prop, 0, "rna_Brush_dyntopo_update"); + + prop = RNA_def_property(srna, "repeat", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, nullptr, "repeat"); + RNA_def_property_range(prop, 0.0f, 15.0f); + RNA_def_property_ui_text(prop, "Repeat", "How many times to run the dyntopo remesher."); + RNA_def_property_update(prop, 0, "rna_Brush_dyntopo_update"); + + prop = RNA_def_property(srna, "quality", PROP_FLOAT, PROP_PERCENTAGE); + RNA_def_property_float_sdna(prop, nullptr, "quality"); + RNA_def_property_range(prop, 0.0f, 10.0f); + RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.01, 3); + RNA_def_property_ui_text( + prop, "Quality", "Lower values are faster but make lower-quality geometry"); + RNA_def_property_update(prop, 0, "rna_Brush_dyntopo_update"); +} + static void rna_def_sculpt_capabilities(BlenderRNA *brna) { StructRNA *srna; @@ -2592,6 +2801,7 @@ static void rna_def_brush(BlenderRNA *brna) {BRUSH_BOUNDARY_DEFORM_GRAB, "GRAB", 0, "Grab", ""}, {BRUSH_BOUNDARY_DEFORM_TWIST, "TWIST", 0, "Twist", ""}, {BRUSH_BOUNDARY_DEFORM_SMOOTH, "SMOOTH", 0, "Smooth", ""}, + {BRUSH_BOUNDARY_DEFORM_CIRCLE, "CIRCLE", 0, "Circle", ""}, {0, nullptr, 0, nullptr, nullptr}, }; @@ -2705,6 +2915,7 @@ static void rna_def_brush(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Curve Preset", ""); RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_CURVES); /* Abusing id_curves :/ */ RNA_def_property_update(prop, 0, "rna_Brush_update"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); prop = RNA_def_property(srna, "deform_target", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, brush_deformation_target_items); @@ -2758,6 +2969,12 @@ static void rna_def_brush(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Deformation", "Deformation type that is used in the brush"); RNA_def_property_update(prop, 0, "rna_Brush_update"); + prop = RNA_def_property(srna, "smear_deform_blend", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, nullptr, "smear_deform_blend"); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_ui_text(prop, "Blend", "Smear deformation blend"); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + prop = RNA_def_property(srna, "slide_deform_type", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, brush_slide_deform_type_items); RNA_def_property_ui_text(prop, "Deformation", "Deformation type that is used in the brush"); @@ -2793,6 +3010,7 @@ static void rna_def_brush(BlenderRNA *brna) RNA_def_property_enum_items(prop, falloff_shape_unit_items); RNA_def_property_ui_text(prop, "Falloff Shape", "Use projected or spherical falloff"); RNA_def_property_update(prop, 0, "rna_Brush_update"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); /* number values */ prop = RNA_def_property(srna, "size", PROP_INT, PROP_PIXEL); @@ -2843,6 +3061,22 @@ static void rna_def_brush(BlenderRNA *brna) prop, "Spacing", "Spacing between brush daubs as a percentage of brush diameter"); RNA_def_property_update(prop, 0, "rna_Brush_update"); + prop = RNA_def_property(srna, "auto_smooth_spacing", PROP_INT, PROP_PERCENTAGE); + RNA_def_property_int_sdna(prop, nullptr, "autosmooth_spacing"); + RNA_def_property_range(prop, 1, 1000); + RNA_def_property_ui_range(prop, 1, 500, 5, -1); + RNA_def_property_ui_text( + prop, "Auto-Smooth Spacing", "Autosmooth spacing as a percentage of brush diameter"); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + + prop = RNA_def_property(srna, "topology_rake_spacing", PROP_INT, PROP_PERCENTAGE); + RNA_def_property_int_sdna(prop, nullptr, "topology_rake_spacing"); + RNA_def_property_range(prop, 1, 1000); + RNA_def_property_ui_range(prop, 1, 500, 5, -1); + RNA_def_property_ui_text( + prop, "Topology Rake Spacing", "Topology rake spacing as a percentage of brush diameter"); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + prop = RNA_def_property(srna, "grad_spacing", PROP_INT, PROP_PIXEL); RNA_def_property_int_sdna(prop, nullptr, "gradient_spacing"); RNA_def_property_range(prop, 1, 10000); @@ -3248,13 +3482,45 @@ static void rna_def_brush(BlenderRNA *brna) RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.001, 3); RNA_def_property_ui_text( prop, "Auto-Smooth", "Amount of smoothing to automatically apply to each stroke"); + + RNA_def_property_update(prop, 0, "rna_Brush_update"); + + prop = RNA_def_property(srna, "auto_smooth_projection", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, nullptr, "autosmooth_projection"); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.001, 3); + RNA_def_property_ui_text(prop, "Preserve Volume", "How much autosmooth should preserve volume."); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + + prop = RNA_def_property(srna, "sharp_angle_limit", PROP_FLOAT, PROP_ANGLE); + RNA_def_property_float_sdna(prop, nullptr, "sharp_angle_limit"); + RNA_def_property_range(prop, 0.0f, M_PI); + RNA_def_property_ui_text(prop, "Sharp Limit", ""); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + + RNA_def_property_update(prop, 0, "rna_Brush_update"); + prop = RNA_def_property(srna, "auto_smooth_radius_factor", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, nullptr, "autosmooth_radius_factor"); + RNA_def_property_range(prop, 0.001f, 5.0f); + RNA_def_property_ui_range(prop, 0.001f, 2.0f, 0.15, 3); + RNA_def_property_ui_text(prop, + "Smooth Radius", + "Ratio between the brush radius and the radius that is going to be " + "used for smoothing"); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + + prop = RNA_def_property(srna, "concave_mask_factor", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, nullptr, "concave_mask_factor"); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.001, 3); + RNA_def_property_ui_text(prop, "Cavity Mask", "Mask to concave areas"); RNA_def_property_update(prop, 0, "rna_Brush_update"); prop = RNA_def_property(srna, "topology_rake_factor", PROP_FLOAT, PROP_FACTOR); RNA_def_property_float_sdna(prop, nullptr, "topology_rake_factor"); RNA_def_property_float_default(prop, 0); - RNA_def_property_range(prop, 0.0f, 1.0f); - RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.001, 3); + RNA_def_property_range(prop, 0.0f, 5.0f); + RNA_def_property_ui_range(prop, 0.0f, 2.0f, 0.001, 3); RNA_def_property_ui_text(prop, "Topology Rake", "Automatically align edges to the brush direction to " @@ -3262,6 +3528,42 @@ static void rna_def_brush(BlenderRNA *brna) "Best used on low-poly meshes as it has a performance impact"); RNA_def_property_update(prop, 0, "rna_Brush_update"); + prop = RNA_def_property(srna, "topology_rake_radius_factor", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, nullptr, "topology_rake_radius_factor"); + RNA_def_property_range(prop, 0.001f, 5.0f); + RNA_def_property_ui_range(prop, 0.0f, 3.0f, 0.1, 2); + RNA_def_property_ui_text(prop, + "Rake Radius", + "Ratio between the brush radius and the radius that is going to be " + "used for topology rake"); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + + prop = RNA_def_property(srna, "topology_rake_projection", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, nullptr, "topology_rake_projection"); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.001, 3); + RNA_def_property_ui_text(prop, + "Projection", + "How much topology rake should stick to surface" + "Lower values with have smoothing effect"); + + RNA_def_property_update(prop, 0, "rna_Brush_update"); + + prop = RNA_def_property(srna, "boundary_smooth_factor", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, nullptr, "boundary_smooth_factor"); + RNA_def_property_float_default(prop, 0); + RNA_def_property_range(prop, -2.0f, 2.0f); + RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.001, 3); + RNA_def_property_ui_text(prop, "Boundary Smoothing", "How much to smooth sharp boundaries "); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + + prop = RNA_def_property(srna, "hard_corner_pin", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, nullptr, "hard_corner_pin"); + RNA_def_property_range(prop, -2.0f, 2.0f); + RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.001, 3); + RNA_def_property_ui_text(prop, "Corner Pin", "How much to pin corners in hard edge mode."); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + prop = RNA_def_property(srna, "tilt_strength_factor", PROP_FLOAT, PROP_FACTOR); RNA_def_property_float_sdna(prop, nullptr, "tilt_strength_factor"); RNA_def_property_float_default(prop, 0); @@ -3408,6 +3710,7 @@ static void rna_def_brush(BlenderRNA *brna) RNA_def_property_range(prop, 0.0f, 5.0f); RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.1, 3); RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); + RNA_def_property_update(prop, 0, "rna_Brush_update"); prop = RNA_def_property(srna, "automasking_cavity_blur_steps", PROP_INT, PROP_NONE); @@ -3492,12 +3795,52 @@ static void rna_def_brush(BlenderRNA *brna) "Apply the maximum grab strength to the active vertex instead of the cursor location"); RNA_def_property_update(prop, 0, "rna_Brush_update"); + prop = RNA_def_property(srna, "use_weighted_smooth", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag2", BRUSH_SMOOTH_USE_AREA_WEIGHT); + RNA_def_property_ui_text(prop, "Weight By Area", "Weight by face area to get a smoother result"); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + + prop = RNA_def_property(srna, "hard_edge_mode", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag2", BRUSH_HARD_EDGE_MODE); + RNA_def_property_ui_text( + prop, "Hard Edge Mode", "Hard edge mode; treat all face set boundaries as hard edges"); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + prop = RNA_def_property(srna, "use_grab_silhouette", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, nullptr, "flag2", BRUSH_GRAB_SILHOUETTE); RNA_def_property_ui_text( prop, "Grab Silhouette", "Grabs trying to automask the silhouette of the object"); RNA_def_property_update(prop, 0, "rna_Brush_update"); + prop = RNA_def_property(srna, "use_curvature_rake", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag2", BRUSH_CURVATURE_RAKE); + RNA_def_property_ui_text( + prop, "Curvature Rake", "Topology rake follows curvature instead of brush direction"); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + + prop = RNA_def_property(srna, "ignore_falloff_for_topology_rake", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag2", BRUSH_TOPOLOGY_RAKE_IGNORE_BRUSH_FALLOFF); + RNA_def_property_ui_text( + prop, "Ignore Brush Falloff", "Ignore brush falloff settings for topology rake"); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + + prop = RNA_def_property(srna, "use_custom_auto_smooth_spacing", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag2", BRUSH_CUSTOM_AUTOSMOOTH_SPACING); + RNA_def_property_ui_text( + prop, + "Use Custom Autosmooth Spacing", + "Use custom spacing for autosmooth (must be larger then brush spacing)"); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + + prop = RNA_def_property(srna, "use_custom_topology_rake_spacing", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag2", BRUSH_CUSTOM_TOPOLOGY_RAKE_SPACING); + RNA_def_property_ui_text( + prop, + "Use Custom Rake Spacing", + "Use custom spacing for topology rake (must be larger then brush spacing)"); + + RNA_def_property_update(prop, 0, "rna_Brush_update"); + prop = RNA_def_property(srna, "use_paint_antialiasing", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, nullptr, "sampling_flag", BRUSH_PAINT_ANTIALIASING); RNA_def_property_ui_text(prop, "Anti-Aliasing", "Smooths the edges of the strokes"); @@ -3722,6 +4065,17 @@ static void rna_def_brush(BlenderRNA *brna) RNA_def_property_flag(prop, PROP_NEVER_NULL); RNA_def_property_ui_text(prop, "Curve", "Editable falloff curve"); RNA_def_property_update(prop, 0, "rna_Brush_update"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); + + prop = RNA_def_property(srna, "pressure_size_curve", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_NEVER_NULL); + RNA_def_property_ui_text(prop, "Pressure/Size Curve", "Pressure/Size input curve"); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + + prop = RNA_def_property(srna, "pressure_strength_curve", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_NEVER_NULL); + RNA_def_property_ui_text(prop, "Pressure/Strength Curve", "Pressure/Strength input curve"); + RNA_def_property_update(prop, 0, "rna_Brush_update"); prop = RNA_def_property(srna, "paint_curve", PROP_POINTER, PROP_NONE); RNA_def_property_flag(prop, PROP_EDITABLE); @@ -3939,6 +4293,12 @@ static void rna_def_brush(BlenderRNA *brna) RNA_def_property_clear_flag(prop, PROP_EDITABLE); RNA_def_property_ui_text(prop, "Gpencil Settings", ""); + prop = RNA_def_property(srna, "dyntopo", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "DynTopoSettings"); + RNA_def_property_pointer_sdna(prop, nullptr, "dyntopo"); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text(prop, "Dyntopo Settings", ""); + prop = RNA_def_property(srna, "curves_sculpt_settings", PROP_POINTER, PROP_NONE); RNA_def_property_struct_type(prop, "BrushCurvesSculptSettings"); RNA_def_property_clear_flag(prop, PROP_EDITABLE); @@ -4018,10 +4378,21 @@ static void rna_def_operator_stroke_element(BlenderRNA *brna) /* XXX: i don't think blender currently supports the ability to properly do a remappable modifier * in the middle of a stroke */ + + prop = RNA_def_property(srna, "mouse_cubic", PROP_FLOAT, PROP_COORDS); + RNA_def_property_flag(prop, PROP_IDPROPERTY); + RNA_def_property_array(prop, 8); + RNA_def_property_ui_text(prop, "Mouse", ""); + + prop = RNA_def_property(srna, "world_cubic", PROP_FLOAT, PROP_COORDS); + RNA_def_property_flag(prop, PROP_IDPROPERTY); + RNA_def_property_array(prop, 12); + RNA_def_property_ui_text(prop, "Mouse", ""); } void RNA_def_brush(BlenderRNA *brna) { + rna_def_dyntopo_settings(brna); rna_def_brush(brna); rna_def_brush_capabilities(brna); rna_def_sculpt_capabilities(brna); diff --git a/source/blender/makesrna/intern/rna_material.cc b/source/blender/makesrna/intern/rna_material.cc index e1b8de5ae69..3ac3222c711 100644 --- a/source/blender/makesrna/intern/rna_material.cc +++ b/source/blender/makesrna/intern/rna_material.cc @@ -153,6 +153,7 @@ static void rna_Material_texpaint_begin(CollectionPropertyIterator *iter, Pointe static void rna_Material_active_paint_texture_index_update(bContext *C, PointerRNA *ptr) { + bScreen *screen; Main *bmain = CTX_data_main(C); Material *ma = (Material *)ptr->owner_id; diff --git a/source/blender/makesrna/intern/rna_mesh.cc b/source/blender/makesrna/intern/rna_mesh.cc index 892d1a85ee1..3d10aa46641 100644 --- a/source/blender/makesrna/intern/rna_mesh.cc +++ b/source/blender/makesrna/intern/rna_mesh.cc @@ -2971,7 +2971,7 @@ static void rna_def_mesh(BlenderRNA *brna) prop = RNA_def_property(srna, "vertex_normals", PROP_COLLECTION, PROP_NONE); RNA_def_property_struct_type(prop, "MeshNormalValue"); - RNA_def_property_override_flag(prop, PROPOVERRIDE_IGNORE); + RNA_def_property_ui_text(prop, "Vertex Normals", "The normal direction of each vertex, defined as the average of the " @@ -3230,6 +3230,7 @@ static void rna_def_mesh(BlenderRNA *brna) "Mirror the left/right vertex groups when painting. The symmetry axis " "is determined by the symmetry settings"); RNA_def_property_update(prop, 0, "rna_Mesh_update_draw"); + /* End Symmetry */ RNA_define_verify_sdna(false); diff --git a/source/blender/makesrna/intern/rna_scene.cc b/source/blender/makesrna/intern/rna_scene.cc index a622916d158..fd9c7660ccf 100644 --- a/source/blender/makesrna/intern/rna_scene.cc +++ b/source/blender/makesrna/intern/rna_scene.cc @@ -17,6 +17,7 @@ #include "DNA_modifier_types.h" #include "DNA_particle_types.h" #include "DNA_rigidbody_types.h" +#include "DNA_scene_enums.h" #include "DNA_scene_types.h" #include "DNA_screen_types.h" /* TransformOrientation */ #include "DNA_userdef_types.h" @@ -3193,6 +3194,11 @@ static void rna_def_tool_settings(BlenderRNA *brna) RNA_def_struct_path_func(srna, "rna_ToolSettings_path"); RNA_def_struct_ui_text(srna, "Tool Settings", ""); + /* NotForPR: used to debug interpolation of original coordinates in dyntopo. */ + prop = RNA_def_property(srna, "show_origco", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "show_origco", 1); + RNA_def_property_ui_text(prop, "Show Original", ""); + prop = RNA_def_property(srna, "sculpt", PROP_POINTER, PROP_NONE); RNA_def_property_struct_type(prop, "Sculpt"); RNA_def_property_ui_text(prop, "Sculpt", ""); @@ -4207,6 +4213,24 @@ static void rna_def_unified_paint_settings(BlenderRNA *brna) StructRNA *srna; PropertyRNA *prop; + static EnumPropertyItem distort_correction_items[] = { + {UNDISTORT_NONE, + "DISABLED", + 0, + "Disabled", + "Disable attribute distortion correction\nto increase performance."}, + {UNDISTORT_REPROJECT_VERTS | UNDISTORT_REPROJECT_CORNERS, + "REPROJECT", + 0, + "Reproject", + "Reproject attributes."}, + {UNDISTORT_REPROJECT_VERTS | UNDISTORT_REPROJECT_CORNERS | UNDISTORT_RELAX_UVS, + "RELAX_UVS", + 0, + "Relax UVs", + "Relax UVs, reproject other attributes."}, + {0, nullptr, 0, nullptr, nullptr}}; + static const EnumPropertyItem brush_size_unit_items[] = { {0, "VIEW", 0, "View", "Measure brush size relative to the view"}, {UNIFIED_PAINT_BRUSH_LOCK_SIZE, @@ -4229,6 +4253,43 @@ static void rna_def_unified_paint_settings(BlenderRNA *brna) "Use Unified Radius", "Instead of per-brush radius, the radius is shared across brushes"); + prop = RNA_def_property(srna, "distort_correction_mode", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, nullptr, "distort_correction_mode"); + RNA_def_property_enum_items(prop, distort_correction_items); + RNA_def_property_ui_text( + prop, "Distortion Correction", "How smooth tools should correct attribute distortion."); + + /* high-level flags to enable or disable unified paint settings */ + prop = RNA_def_property(srna, "use_unified_hard_edge_mode", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag", UNIFIED_PAINT_FLAG_HARD_EDGE_MODE); + RNA_def_property_ui_text( + prop, "Use Unified Hard Edge Mode", "Use global setting for hard edge mode"); + + prop = RNA_def_property(srna, "use_unified_hard_corner_pin", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag", UNIFIED_PAINT_HARD_CORNER_PIN); + RNA_def_property_ui_text( + prop, "Use Unified Hard Corner Pin", "Use global setting for hard corner pin"); + + prop = RNA_def_property(srna, "hard_corner_pin", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, nullptr, "hard_corner_pin"); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_ui_text( + prop, "Use Unified Hard Corner Pin", "Use global setting for hard corner pin"); + RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE); + RNA_def_property_update(prop, 0, "rna_UnifiedPaintSettings_update"); + + prop = RNA_def_property(srna, "use_unified_sharp_angle_limit", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag", UNIFIED_PAINT_FLAG_SHARP_ANGLE_LIMIT); + RNA_def_property_ui_text( + prop, "Use Unified Sharp Angle Limit", "Use global setting for sharp angle limit"); + + prop = RNA_def_property(srna, "sharp_angle_limit", PROP_FLOAT, PROP_ANGLE); + RNA_def_property_float_sdna(prop, nullptr, "sharp_angle_limit"); + RNA_def_property_range(prop, 0.0f, M_PI); + RNA_def_property_ui_text(prop, "Sharp Limit", ""); + RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE); + RNA_def_property_update(prop, 0, "rna_UnifiedPaintSettings_update"); + prop = RNA_def_property(srna, "use_unified_strength", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, nullptr, "flag", UNIFIED_PAINT_ALPHA); RNA_def_property_ui_text(prop, @@ -4319,6 +4380,49 @@ static void rna_def_unified_paint_settings(BlenderRNA *brna) RNA_def_property_enum_items(prop, brush_size_unit_items); RNA_def_property_ui_text( prop, "Radius Unit", "Measure brush size relative to the view or the scene"); + + prop = RNA_def_property(srna, "hard_edge_mode", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "hard_edge_mode", 1); + RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE); + RNA_def_property_ui_text( + prop, "Hard Edge Mode", "Hard edge mode; treat all face set boundaries as hard edges"); + RNA_def_property_update(prop, 0, "rna_UnifiedPaintSettings_update"); + + prop = RNA_def_property(srna, "smooth_boundary_mesh", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "smooth_boundary_flag", SCULPT_BOUNDARY_MESH); + RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE); + RNA_def_property_ui_text(prop, "Mesh", "Open mesh boundaries (holes in the mesh)."); + RNA_def_property_update(prop, 0, "rna_UnifiedPaintSettings_update"); + + prop = RNA_def_property(srna, "smooth_boundary_seam", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "smooth_boundary_flag", SCULPT_BOUNDARY_SEAM); + RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE); + RNA_def_property_ui_text(prop, "Seam", "Seam edges"); + RNA_def_property_update(prop, 0, "rna_UnifiedPaintSettings_update"); + + prop = RNA_def_property(srna, "smooth_boundary_sharp_mark", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "smooth_boundary_flag", SCULPT_BOUNDARY_SHARP_MARK); + RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE); + RNA_def_property_ui_text(prop, "Marked Sharp", "Edges marked as sharp"); + RNA_def_property_update(prop, 0, "rna_UnifiedPaintSettings_update"); + + prop = RNA_def_property(srna, "smooth_boundary_sharp_angle", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "smooth_boundary_flag", SCULPT_BOUNDARY_SHARP_ANGLE); + RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE); + RNA_def_property_ui_text(prop, "Sharp By Angle", "Edges whose face angle exceeds a limit"); + RNA_def_property_update(prop, 0, "rna_UnifiedPaintSettings_update"); + + prop = RNA_def_property(srna, "smooth_boundary_face_set", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "smooth_boundary_flag", SCULPT_BOUNDARY_FACE_SET); + RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE); + RNA_def_property_ui_text(prop, "Face Sets", "Face set boundaries"); + RNA_def_property_update(prop, 0, "rna_UnifiedPaintSettings_update"); + + prop = RNA_def_property(srna, "smooth_boundary_uv", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "smooth_boundary_flag", SCULPT_BOUNDARY_UV); + RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE); + RNA_def_property_ui_text(prop, "UV", "UV island boundaries"); + RNA_def_property_update(prop, 0, "rna_UnifiedPaintSettings_update"); } static void rna_def_curve_paint_settings(BlenderRNA *brna) diff --git a/source/blender/makesrna/intern/rna_sculpt_paint.cc b/source/blender/makesrna/intern/rna_sculpt_paint.cc index 9d35a4863c2..bc7ab278cc0 100644 --- a/source/blender/makesrna/intern/rna_sculpt_paint.cc +++ b/source/blender/makesrna/intern/rna_sculpt_paint.cc @@ -562,6 +562,22 @@ static std::optional rna_GPencilSculptGuide_path(const PointerRNA * return "tool_settings.gpencil_sculpt.guide"; } +bool rna_Sculpt_has_persistent_base(bContext *C) +{ + PaintMode mode = BKE_paintmode_get_active_from_context(C); + + if (mode != PaintMode::Sculpt) { + return false; + } + + Object *ob = CTX_data_active_object(C); + if (!ob || ob->type != OB_MESH || !ob->sculpt) { + return false; + } + + return BKE_sculpt_has_persistent_base(ob->sculpt); +} + static void rna_Sculpt_automasking_invert_cavity_set(PointerRNA *ptr, bool val) { Sculpt *sd = (Sculpt *)ptr->data; @@ -664,6 +680,11 @@ static void rna_def_paint(BlenderRNA *brna) prop, "Fast Navigate", "For multires, show low resolution while navigating the view"); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr); + prop = RNA_def_property(srna, "show_sculpt_pivot", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flags", PAINT_SCULPT_SHOW_PIVOT); + RNA_def_property_ui_text(prop, "Show Pivot", "Show Transform Tool Sculpt Pivot"); + RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr); + prop = RNA_def_property(srna, "use_sculpt_delay_updates", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, nullptr, "flags", PAINT_SCULPT_DELAY_UPDATES); RNA_def_property_ui_text( @@ -826,32 +847,14 @@ static void rna_def_sculpt(BlenderRNA *brna) RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE); RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_Sculpt_update"); - prop = RNA_def_property(srna, "detail_size", PROP_FLOAT, PROP_PIXEL); - RNA_def_property_range(prop, 0.5, 40.0); - RNA_def_property_ui_range(prop, 0.5, 40.0, 0.1, 2); - RNA_def_property_ui_scale_type(prop, PROP_SCALE_CUBIC); - RNA_def_property_ui_text( - prop, "Detail Size", "Maximum edge length for dynamic topology sculpting (in pixels)"); - RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr); + prop = RNA_def_property(srna, "use_dyntopo", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flags", SCULPT_DYNTOPO_ENABLED); + RNA_def_property_ui_text(prop, "DynTopo", "Enable DynTopo remesher in dynamic topology mode."); + RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE); - prop = RNA_def_property(srna, "detail_percent", PROP_FLOAT, PROP_PERCENTAGE); - RNA_def_property_range(prop, 0.5, 100.0); - RNA_def_property_ui_range(prop, 0.5, 100.0, 10, 2); - RNA_def_property_ui_text( - prop, - "Detail Percentage", - "Maximum edge length for dynamic topology sculpting (in brush percenage)"); - RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr); - - prop = RNA_def_property(srna, "constant_detail_resolution", PROP_FLOAT, PROP_NONE); - RNA_def_property_float_sdna(prop, nullptr, "constant_detail"); - RNA_def_property_range(prop, 0.0001, FLT_MAX); - RNA_def_property_ui_range(prop, 0.001, 1000.0, 10, 2); - RNA_def_property_ui_text(prop, - "Resolution", - "Maximum edge length for dynamic topology sculpting (as divisor " - "of Blender unit - higher value means smaller edge length)"); - RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr); + prop = RNA_def_property(srna, "dyntopo", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "DynTopoSettings"); + RNA_def_property_pointer_sdna(prop, nullptr, "dyntopo"); const EnumPropertyItem *entry = rna_enum_brush_automasking_flag_items; do { @@ -996,6 +999,14 @@ static void rna_def_sculpt(BlenderRNA *brna) RNA_def_property_ui_text( prop, "Orientation", "Object whose Z axis defines orientation of gravity"); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr); + + /* functions */ + FunctionRNA *func; + + func = RNA_def_function(srna, "has_persistent_base", "rna_Sculpt_has_persistent_base"); + RNA_def_function_ui_description(func, "Test if sculpt has persistent base (sculpt mode only)"); + RNA_def_function_flag(func, FUNC_USE_CONTEXT | FUNC_NO_SELF); + RNA_def_function_return(func, RNA_def_boolean(func, "has", 1, "Has persistent Base", "")); } static void rna_def_uv_sculpt(BlenderRNA *brna) diff --git a/source/blender/makesrna/intern/rna_wm_api.cc b/source/blender/makesrna/intern/rna_wm_api.cc index c0f818ad378..3210eea4fc2 100644 --- a/source/blender/makesrna/intern/rna_wm_api.cc +++ b/source/blender/makesrna/intern/rna_wm_api.cc @@ -62,6 +62,8 @@ const EnumPropertyItem rna_enum_window_cursor_items[] = { #ifdef RNA_RUNTIME +extern "C" void wm_autosave_write(Main *bmain, wmWindowManager *wm); + # include "BKE_context.hh" # include "BKE_undo_system.hh" @@ -123,6 +125,12 @@ static bool rna_event_modal_handler_add(bContext *C, ReportList *reports, wmOper return WM_event_add_modal_handler_ex(win, area, region, op) != nullptr; } +static void rna_WindowManager_autosave_write(struct wmWindowManager *wm, Main *main) +{ + WM_autosave_write(wm, main); +} + +/* XXX, need a way for python to know event types, 0x0110 is hard coded */ static wmTimer *rna_event_timer_add(wmWindowManager *wm, float time_step, wmWindow *win) { /* NOTE: we need a way for Python to know event types, `TIMER` is hard coded. */ @@ -820,6 +828,10 @@ void RNA_api_wm(StructRNA *srna) FunctionRNA *func; PropertyRNA *parm; + func = RNA_def_function(srna, "autosave_write", "rna_WindowManager_autosave_write"); + RNA_def_function_ui_description(func, "Force autosave "); + RNA_def_function_flag(func, FUNC_USE_MAIN); + func = RNA_def_function(srna, "fileselect_add", "WM_event_add_fileselect"); RNA_def_function_ui_description( func, diff --git a/source/blender/windowmanager/WM_api.hh b/source/blender/windowmanager/WM_api.hh index 0f90cee27d3..251ed5ce17b 100644 --- a/source/blender/windowmanager/WM_api.hh +++ b/source/blender/windowmanager/WM_api.hh @@ -37,6 +37,7 @@ struct ImBuf; struct ImageFormatData; struct Main; struct MenuType; +struct PBVH; struct PointerRNA; struct PropertyRNA; struct ScrArea; diff --git a/source/blender/windowmanager/intern/wm_draw.cc b/source/blender/windowmanager/intern/wm_draw.cc index d828a0bc8ad..75822fdffd9 100644 --- a/source/blender/windowmanager/intern/wm_draw.cc +++ b/source/blender/windowmanager/intern/wm_draw.cc @@ -27,6 +27,8 @@ #include "BKE_context.hh" #include "BKE_image.h" +#include "BKE_main.hh" +#include "BKE_paint.hh" #include "BKE_scene.hh" #include "BKE_screen.hh" diff --git a/source/blender/windowmanager/intern/wm_files.cc b/source/blender/windowmanager/intern/wm_files.cc index f0ff32c72fe..e9d5436e54c 100644 --- a/source/blender/windowmanager/intern/wm_files.cc +++ b/source/blender/windowmanager/intern/wm_files.cc @@ -65,6 +65,7 @@ #include "BKE_appdir.hh" #include "BKE_autoexec.hh" #include "BKE_blender.hh" +#include "BKE_blender_undo.hh" #include "BKE_blender_version.h" #include "BKE_blendfile.hh" #include "BKE_callbacks.hh" @@ -76,7 +77,9 @@ #include "BKE_lib_remap.hh" #include "BKE_main.hh" #include "BKE_main_namemap.hh" +#include "BKE_multires.hh" #include "BKE_packedFile.h" +#include "BKE_paint.hh" #include "BKE_report.hh" #include "BKE_scene.hh" #include "BKE_screen.hh" @@ -2115,6 +2118,16 @@ static void wm_autosave_location(char filepath[FILE_MAX]) BLI_path_join(filepath, FILE_MAX, tempdir_base, filename); } +static bool wm_autosave_supported(UndoStack *stack) { + if (ED_undosys_stack_memfile_get_if_active(stack)) { + return true; + } + if (stack->step_active && stack->step_active->type == BKE_UNDOSYS_TYPE_SCULPT) { + return true; + } + return false; +} + static bool wm_autosave_write_try(Main *bmain, wmWindowManager *wm) { char filepath[FILE_MAX]; @@ -2125,7 +2138,7 @@ static bool wm_autosave_write_try(Main *bmain, wmWindowManager *wm) * compared to when the #MemFile undo step was used for saving undo-steps. So for now just skip * auto-save when we are in a mode where auto-save wouldn't have worked previously anyway. This * check can be removed once the performance regressions have been solved. */ - if (ED_undosys_stack_memfile_get_if_active(wm->undo_stack) != nullptr) { + if (wm_autosave_supported(wm->undo_stack)) { WM_autosave_write(wm, bmain); return true; } diff --git a/source/blender/windowmanager/intern/wm_toolsystem.cc b/source/blender/windowmanager/intern/wm_toolsystem.cc index 8c9c7da6a86..bd099f35145 100644 --- a/source/blender/windowmanager/intern/wm_toolsystem.cc +++ b/source/blender/windowmanager/intern/wm_toolsystem.cc @@ -19,6 +19,7 @@ #include "BLI_utildefines.h" #include "DNA_ID.h" +#include "DNA_brush_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_space_types.h"