Fix #115843: Expose curves sculpt collision distance

The hardcoded value doesn't work well with real scale human heads for
example (was already adjusted once in a76b5d3a07).
The result for too high values is a complete "freeze" of the whole curve
(since  the solution from e7606139ba has the problem that it keeps
running into max iterations of the collision solver).

As long as no better solver is implemented, it is better to have an
adjustable value (to work on differently sizes objects) to not run into
the above issue (same as the old particle hair system had) and show it
in sculptmode next to the button which enables collision.

This is done per `Curves` (same as the flag
`CV_SCULPT_COLLISION_ENABLED`), similar to symmetry settings
[alternatively, it could be part of `BrushCurvesSculptSettings` but I
think it makes more sense in Curves] and then passed on to the
`CurvesConstraintSolver`.

Includes versioning code (to set the default for old files).

Pull Request: https://projects.blender.org/blender/blender/pulls/132997
This commit is contained in:
Philipp Oeser
2025-01-15 15:54:43 +01:00
committed by Philipp Oeser
parent d9d7d3b354
commit e700e1f71c
14 changed files with 55 additions and 17 deletions

View File

@@ -170,7 +170,11 @@ class VIEW3D_HT_tool_header(Header):
sub.prop(ob.data, "use_mirror_y", text="Y", toggle=True)
sub.prop(ob.data, "use_mirror_z", text="Z", toggle=True)
layout.prop(ob.data, "use_sculpt_collision", icon='MOD_PHYSICS', icon_only=True, toggle=True)
row = layout.row(align=True)
row.prop(ob.data, "use_sculpt_collision", icon='MOD_PHYSICS', icon_only=True, toggle=True)
sub = row.row(align=True)
sub.active = ob.data.use_sculpt_collision
sub.prop(ob.data, "surface_collision_distance")
# Expand panels from the side-bar as popovers.
popover_kw = {"space_type": 'VIEW_3D', "region_type": 'UI', "category": "Tool"}

View File

@@ -31,7 +31,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 22
#define BLENDER_FILE_SUBVERSION 23
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

View File

@@ -315,6 +315,7 @@ void curves_copy_parameters(const Curves &src, Curves &dst)
if (src.surface_uv_map != nullptr) {
dst.surface_uv_map = BLI_strdup(src.surface_uv_map);
}
dst.surface_collision_distance = src.surface_collision_distance;
}
CurvesSurfaceTransforms::CurvesSurfaceTransforms(const Object &curves_ob, const Object *surface_ob)

View File

@@ -5638,6 +5638,14 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 404, 23)) {
if (!DNA_struct_member_exists(fd->filesdna, "Curves", "float", "surface_collision_distance")) {
LISTBASE_FOREACH (Curves *, curves, &bmain->hair_curves) {
curves->surface_collision_distance = 0.005f;
}
}
}
/* Always run this versioning; meshes are written with the legacy format which always needs to
* be converted to the new format on file load. Can be moved to a subversion check in a larger
* breaking release. */

View File

@@ -439,9 +439,11 @@ void report_invalid_uv_map(ReportList *reports)
void CurvesConstraintSolver::initialize(const bke::CurvesGeometry &curves,
const IndexMask &curve_selection,
const bool use_surface_collision)
const bool use_surface_collision,
const float surface_collision_distance)
{
use_surface_collision_ = use_surface_collision;
surface_collision_distance_ = surface_collision_distance;
segment_lengths_.reinitialize(curves.points_num());
geometry::curve_constraints::compute_segment_lengths(
curves.points_by_curve(), curves.positions(), curve_selection, segment_lengths_);
@@ -463,7 +465,8 @@ void CurvesConstraintSolver::solve_step(bke::CurvesGeometry &curves,
start_positions_,
*surface,
transforms,
curves.positions_for_write());
curves.positions_for_write(),
surface_collision_distance_);
start_positions_ = curves.positions();
}
else {

View File

@@ -140,8 +140,10 @@ struct CombOperationExecutor {
if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE || (U.uiflag & USER_ORBIT_SELECTION)) {
this->initialize_spherical_brush_reference_point();
}
self_->constraint_solver_.initialize(
*curves_orig_, curve_selection_, curves_id_orig_->flag & CV_SCULPT_COLLISION_ENABLED);
self_->constraint_solver_.initialize(*curves_orig_,
curve_selection_,
curves_id_orig_->flag & CV_SCULPT_COLLISION_ENABLED,
curves_id_orig_->surface_collision_distance);
self_->curve_lengths_.reinitialize(curves_orig_->curves_num());
const Span<float> segment_lengths = self_->constraint_solver_.segment_lengths();

View File

@@ -162,13 +162,15 @@ void report_invalid_uv_map(ReportList *reports);
struct CurvesConstraintSolver {
private:
bool use_surface_collision_;
float surface_collision_distance_;
Array<float3> start_positions_;
Array<float> segment_lengths_;
public:
void initialize(const bke::CurvesGeometry &curves,
const IndexMask &curve_selection,
const bool use_surface_collision);
const bool use_surface_collision,
const float surface_collision_distance);
void solve_step(bke::CurvesGeometry &curves,
const IndexMask &curve_selection,

View File

@@ -125,8 +125,10 @@ struct PinchOperationExecutor {
math::transform_point(transforms_.curves_to_world, self_->brush_3d_.position_cu));
}
self_->constraint_solver_.initialize(
*curves_, curve_selection_, curves_id_->flag & CV_SCULPT_COLLISION_ENABLED);
self_->constraint_solver_.initialize(*curves_,
curve_selection_,
curves_id_->flag & CV_SCULPT_COLLISION_ENABLED,
curves_id_->surface_collision_distance);
}
Array<bool> changed_curves(curves_->curves_num(), false);

View File

@@ -134,8 +134,10 @@ struct PuffOperationExecutor {
math::transform_point(transforms_.curves_to_world, self_->brush_3d_.position_cu));
}
self_->constraint_solver_.initialize(
*curves_, curve_selection_, curves_id_->flag & CV_SCULPT_COLLISION_ENABLED);
self_->constraint_solver_.initialize(*curves_,
curve_selection_,
curves_id_->flag & CV_SCULPT_COLLISION_ENABLED,
curves_id_->surface_collision_distance);
}
Array<float> curve_weights(curves_->curves_num(), 0.0f);

View File

@@ -24,6 +24,7 @@ void solve_length_and_collision_constraints(OffsetIndices<int> points_by_curve,
Span<float3> start_positions,
const Mesh &surface,
const bke::CurvesSurfaceTransforms &transforms,
MutableSpan<float3> positions);
MutableSpan<float3> positions,
const float surface_collision_distance);
} // namespace blender::geometry::curve_constraints

View File

@@ -67,13 +67,13 @@ void solve_length_and_collision_constraints(const OffsetIndices<int> points_by_c
const Span<float3> start_positions_cu,
const Mesh &surface,
const bke::CurvesSurfaceTransforms &transforms,
MutableSpan<float3> positions_cu)
MutableSpan<float3> positions_cu,
const float surface_collision_distance)
{
solve_length_constraints(points_by_curve, curve_selection, segment_lengths_cu, positions_cu);
blender::bke::BVHTreeFromMesh surface_bvh = surface.bvh_corner_tris();
const float radius = 0.005f;
const int max_collisions = 5;
curve_selection.foreach_segment(GrainSize(64), [&](const IndexMaskSegment segment) {
@@ -108,11 +108,11 @@ void solve_length_and_collision_constraints(const OffsetIndices<int> points_by_c
max_ray_length_su);
BVHTreeRayHit hit;
hit.index = -1;
hit.dist = max_ray_length_su + radius;
hit.dist = max_ray_length_su + surface_collision_distance;
BLI_bvhtree_ray_cast(surface_bvh.tree,
start_pos_su,
ray_direction_su,
radius,
surface_collision_distance,
&hit,
surface_bvh.raycast_callback,
&surface_bvh);
@@ -135,7 +135,7 @@ void solve_length_and_collision_constraints(const OffsetIndices<int> points_by_c
math::transform_direction(transforms.surface_to_curves_normal, hit_normal_su));
/* Slide on a plane that is slightly above the surface. */
const float3 plane_pos_cu = hit_pos_cu + hit_normal_cu * radius;
const float3 plane_pos_cu = hit_pos_cu + hit_normal_cu * surface_collision_distance;
const float3 plane_normal_cu = hit_normal_cu;
/* Decompose the current segment into the part normal and tangent to the collision

View File

@@ -17,6 +17,7 @@
#define _DNA_DEFAULT_Curves \
{ \
.flag = 0, \
.surface_collision_distance = 0.005f, \
}
/** \} */

View File

@@ -204,6 +204,10 @@ typedef struct Curves {
*/
char *surface_uv_map;
/* Distance to keep the curves away from the surface. */
float surface_collision_distance;
char _pad2[4];
/* Draw cache to store data used for viewport drawing. */
void *batch_cache;
} Curves;

View File

@@ -552,6 +552,14 @@ static void rna_def_curves(BlenderRNA *brna)
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_update(prop, 0, "rna_Curves_update_draw");
prop = RNA_def_property(srna, "surface_collision_distance", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_float_sdna(prop, nullptr, "surface_collision_distance");
RNA_def_property_range(prop, FLT_EPSILON, FLT_MAX);
RNA_def_property_ui_range(prop, 0.0, 10.0f, 0.001, 3);
RNA_def_property_ui_text(
prop, "Collision distance", "Distance to keep the curves away from the surface");
RNA_def_property_update(prop, 0, "rna_Curves_update_draw");
/* attributes */
rna_def_attributes_common(srna, AttributeOwnerType::Curves);