Fix #101766: support orbit-around-selection in curves sculpt mode

How the new navigation pivot is determined depends a bit on the kind of brush:
* Brushes that deform or remove curves use the 3d-brush position at the start of
  the brush.
* Brushes that add new curves set the pivot to the bounding box center of the
  new curves.

Finding a good pivot point is not super trivial for curves, but the existing 3d
brush functionality seems to work well. This also has the benefit that almost no
additional computation is needed when the user is using the spherical brush
mode. However, if the projected mode is used, and orbit-around-selection is on,
then we have to compute the spherical brush center now anyway.

Pull Request: https://projects.blender.org/blender/blender/pulls/131907
This commit is contained in:
Jacques Lucke
2024-12-17 15:29:40 +01:00
parent 6924be003e
commit 97fe1387ae
14 changed files with 72 additions and 9 deletions

View File

@@ -6,6 +6,7 @@
#include "curves_sculpt_intern.hh"
#include "BLI_bounds.hh"
#include "BLI_kdtree.h"
#include "BLI_math_geom.h"
#include "BLI_math_matrix.hh"
@@ -239,6 +240,14 @@ struct AddOperationExecutor {
add_outputs.new_curves_range));
selection.finish();
}
if (U.uiflag & USER_ORBIT_SELECTION) {
if (const std::optional<Bounds<float3>> center_cu = bounds::min_max(
curves_orig_->positions().slice(add_outputs.new_points_range)))
{
remember_stroke_position(
*ctx_.scene, math::transform_point(transforms_.curves_to_world, center_cu->center()));
}
}
if (add_outputs.uv_error) {
report_invalid_uv_map(stroke_extension.reports);

View File

@@ -346,6 +346,14 @@ Vector<float4x4> get_symmetry_brush_transforms(const eCurvesSymmetryType symmetr
return matrices;
}
void remember_stroke_position(Scene &scene, const float3 &brush_position_wo)
{
UnifiedPaintSettings &ups = scene.toolsettings->unified_paint_settings;
copy_v3_v3(ups.average_stroke_accum, brush_position_wo);
ups.average_stroke_counter = 1;
ups.last_stroke_valid = true;
}
float transform_brush_radius(const float4x4 &transform,
const float3 &brush_position,
const float old_radius)

View File

@@ -137,7 +137,7 @@ struct CombOperationExecutor {
brush_pos_diff_re_ = brush_pos_re_ - brush_pos_prev_re_;
if (stroke_extension.is_first) {
if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE || (U.uiflag & USER_ORBIT_SELECTION)) {
this->initialize_spherical_brush_reference_point();
}
self_->constraint_solver_.initialize(
@@ -392,6 +392,9 @@ struct CombOperationExecutor {
brush_radius_base_re_);
if (brush_3d.has_value()) {
self_->brush_3d_ = *brush_3d;
remember_stroke_position(
*ctx_.scene,
math::transform_point(transforms_.curves_to_world, self_->brush_3d_.position_cu));
}
}
};

View File

@@ -106,7 +106,7 @@ struct DeleteOperationExecutor {
const eBrushFalloffShape falloff_shape = eBrushFalloffShape(brush_->falloff_shape);
if (stroke_extension.is_first) {
if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE || (U.uiflag & USER_ORBIT_SELECTION)) {
this->initialize_spherical_brush_reference_point();
}
const bke::crazyspace::GeometryDeformation deformation =
@@ -261,6 +261,9 @@ struct DeleteOperationExecutor {
brush_radius_base_re_);
if (brush_3d.has_value()) {
self_->brush_3d_ = *brush_3d;
remember_stroke_position(
*ctx_.scene,
math::transform_point(transforms_.curves_to_world, self_->brush_3d_.position_cu));
}
}
};

View File

@@ -16,6 +16,7 @@
#include "BKE_object.hh"
#include "BKE_paint.hh"
#include "BKE_report.hh"
#include "BLI_bounds.hh"
#include "ED_screen.hh"
#include "ED_view3d.hh"
@@ -287,6 +288,14 @@ struct DensityAddOperationExecutor {
add_outputs.new_curves_range));
selection.finish();
}
if (U.uiflag & USER_ORBIT_SELECTION) {
if (const std::optional<Bounds<float3>> center_cu = bounds::min_max(
curves_orig_->positions().slice(add_outputs.new_points_range)))
{
remember_stroke_position(
*ctx_.scene, math::transform_point(transforms_.curves_to_world, center_cu->center()));
}
}
if (add_outputs.uv_error) {
report_invalid_uv_map(stroke_extension.reports);

View File

@@ -290,7 +290,7 @@ struct CurvesEffectOperationExecutor {
brush_pos_end_re_ = stroke_extension.mouse_position;
if (stroke_extension.is_first) {
if (falloff_shape_ == PAINT_FALLOFF_SHAPE_SPHERE) {
if (falloff_shape_ == PAINT_FALLOFF_SHAPE_SPHERE || (U.flag & USER_ORBIT_SELECTION)) {
if (std::optional<CurvesBrush3D> brush_3d = sample_curves_3d_brush(
*ctx_.depsgraph,
*ctx_.region,
@@ -301,6 +301,9 @@ struct CurvesEffectOperationExecutor {
brush_radius_base_re_))
{
self.brush_3d_ = *brush_3d;
remember_stroke_position(
*ctx_.scene,
math::transform_point(transforms_.curves_to_world, self_->brush_3d_.position_cu));
}
}

View File

@@ -92,6 +92,12 @@ std::optional<CurvesBrush3D> sample_curves_3d_brush(const Depsgraph &depsgraph,
const float2 &brush_pos_re,
const float brush_radius_re);
/**
* Updates the position of the stroke so that it can be used by the orbit-around-selection
* navigation method.
*/
void remember_stroke_position(Scene &scene, const float3 &brush_position_wo);
Vector<float4x4> get_symmetry_brush_transforms(eCurvesSymmetryType symmetry);
bke::SpanAttributeWriter<float> float_selection_ensure(Curves &curves_id);
@@ -117,7 +123,7 @@ void move_last_point_and_resample(MoveAndResampleBuffers &buffer,
class CurvesSculptCommonContext {
public:
const Depsgraph *depsgraph = nullptr;
const Scene *scene = nullptr;
Scene *scene = nullptr;
ARegion *region = nullptr;
const View3D *v3d = nullptr;
RegionView3D *rv3d = nullptr;

View File

@@ -112,7 +112,7 @@ struct PinchOperationExecutor {
const eBrushFalloffShape falloff_shape = eBrushFalloffShape(brush_->falloff_shape);
if (stroke_extension.is_first) {
if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE || (U.uiflag & USER_ORBIT_SELECTION)) {
self_->brush_3d_ = *sample_curves_3d_brush(*ctx_.depsgraph,
*ctx_.region,
*ctx_.v3d,
@@ -120,6 +120,9 @@ struct PinchOperationExecutor {
*object_,
brush_pos_re_,
brush_radius_base_re_);
remember_stroke_position(
*ctx_.scene,
math::transform_point(transforms_.curves_to_world, self_->brush_3d_.position_cu));
}
self_->constraint_solver_.initialize(

View File

@@ -121,7 +121,7 @@ struct PuffOperationExecutor {
surface_bvh_ = surface_->bvh_corner_tris();
if (stroke_extension.is_first) {
if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE || (U.uiflag & USER_ORBIT_SELECTION)) {
self.brush_3d_ = *sample_curves_3d_brush(*ctx_.depsgraph,
*ctx_.region,
*ctx_.v3d,
@@ -129,6 +129,9 @@ struct PuffOperationExecutor {
*object_,
brush_pos_re_,
brush_radius_base_re_);
remember_stroke_position(
*ctx_.scene,
math::transform_point(transforms_.curves_to_world, self_->brush_3d_.position_cu));
}
self_->constraint_solver_.initialize(

View File

@@ -115,7 +115,7 @@ struct SelectionPaintOperationExecutor {
selection_goal_ = self_->use_select_ ? 1.0f : 0.0f;
if (stroke_extension.is_first) {
if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE || (U.uiflag & USER_ORBIT_SELECTION)) {
this->initialize_spherical_brush_reference_point();
}
}
@@ -377,6 +377,9 @@ struct SelectionPaintOperationExecutor {
brush_radius_base_re_);
if (brush_3d.has_value()) {
self_->brush_3d_ = *brush_3d;
remember_stroke_position(
*ctx_.scene,
math::transform_point(transforms_.curves_to_world, self_->brush_3d_.position_cu));
}
}
};

View File

@@ -239,6 +239,9 @@ struct SlideOperationExecutor {
if (!brush_3d.has_value()) {
return;
}
remember_stroke_position(
*ctx_.scene, math::transform_point(transforms_.curves_to_world, brush_3d->position_cu));
const ReverseUVSampler reverse_uv_sampler_orig{surface_uv_map_orig_,
surface_corner_tris_orig_};
for (const float4x4 &brush_transform : brush_transforms) {

View File

@@ -87,7 +87,7 @@ struct SmoothOperationExecutor {
const eBrushFalloffShape falloff_shape = eBrushFalloffShape(brush_->falloff_shape);
if (stroke_extension.is_first) {
if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE || (U.uiflag & USER_ORBIT_SELECTION)) {
self.brush_3d_ = *sample_curves_3d_brush(*ctx_.depsgraph,
*ctx_.region,
*ctx_.v3d,
@@ -95,6 +95,9 @@ struct SmoothOperationExecutor {
*object_,
brush_pos_re_,
brush_radius_base_re_);
remember_stroke_position(
*ctx_.scene,
math::transform_point(transforms_.curves_to_world, self_->brush_3d_.position_cu));
}
}

View File

@@ -129,7 +129,7 @@ struct SnakeHookOperatorExecutor {
brush_pos_diff_re_ = brush_pos_re_ - brush_pos_prev_re_;
if (stroke_extension.is_first) {
if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE || (U.uiflag & USER_ORBIT_SELECTION)) {
std::optional<CurvesBrush3D> brush_3d = sample_curves_3d_brush(*ctx_.depsgraph,
*ctx_.region,
*ctx_.v3d,
@@ -139,6 +139,9 @@ struct SnakeHookOperatorExecutor {
brush_radius_base_re_);
if (brush_3d.has_value()) {
self_->brush_3d_ = *brush_3d;
remember_stroke_position(
*ctx_.scene,
math::transform_point(transforms_.curves_to_world, self_->brush_3d_.position_cu));
}
}
return;

View File

@@ -801,6 +801,10 @@ bool view3d_orbit_calc_center(bContext *C, float r_dyn_ofs[3])
BKE_paint_stroke_get_average(scene, ob_act_eval, lastofs);
is_set = true;
}
else if (ob_act && (ob_act->mode & OB_MODE_SCULPT_CURVES)) {
BKE_paint_stroke_get_average(scene, ob_act_eval, lastofs);
is_set = true;
}
else if (ob_act && (ob_act->mode & OB_MODE_EDIT) && (ob_act->type == OB_FONT)) {
Curve *cu = static_cast<Curve *>(ob_act_eval->data);
EditFont *ef = cu->editfont;