Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/sculpt_paint/curves_sculpt_puff.cc
| Show All 16 Lines | |||||
| #include "DNA_mesh_types.h" | #include "DNA_mesh_types.h" | ||||
| #include "DNA_meshdata_types.h" | #include "DNA_meshdata_types.h" | ||||
| #include "WM_api.h" | #include "WM_api.h" | ||||
| #include "BLI_length_parameterize.hh" | #include "BLI_length_parameterize.hh" | ||||
| #include "GEO_add_curves_on_mesh.hh" | #include "GEO_add_curves_on_mesh.hh" | ||||
| #include "GEO_constraint_solver.hh" | |||||
| #include "curves_sculpt_intern.hh" | #include "curves_sculpt_intern.hh" | ||||
| namespace blender::ed::sculpt_paint { | namespace blender::ed::sculpt_paint { | ||||
| using geometry::ConstraintSolver; | |||||
| class PuffOperation : public CurvesSculptStrokeOperation { | class PuffOperation : public CurvesSculptStrokeOperation { | ||||
| private: | private: | ||||
| /** Only used when a 3D brush is used. */ | /** Only used when a 3D brush is used. */ | ||||
| CurvesBrush3D brush_3d_; | CurvesBrush3D brush_3d_; | ||||
| /** Length of each segment indexed by the index of the first point in the segment. */ | /** Solver for length and contact constraints. */ | ||||
| Array<float> segment_lengths_cu_; | ConstraintSolver constraint_solver_; | ||||
| friend struct PuffOperationExecutor; | friend struct PuffOperationExecutor; | ||||
| public: | public: | ||||
| void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override; | void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override; | ||||
| }; | }; | ||||
| /** | /** | ||||
| * Utility class that actually executes the update when the stroke is updated. That's useful | * Utility class that actually executes the update when the stroke is updated. That's useful | ||||
| * because it avoids passing a very large number of parameters between functions. | * because it avoids passing a very large number of parameters between functions. | ||||
| */ | */ | ||||
| struct PuffOperationExecutor { | struct PuffOperationExecutor { | ||||
| PuffOperation *self_ = nullptr; | PuffOperation *self_ = nullptr; | ||||
| CurvesSculptCommonContext ctx_; | CurvesSculptCommonContext ctx_; | ||||
| Object *object_ = nullptr; | Object *object_ = nullptr; | ||||
| Curves *curves_id_ = nullptr; | Curves *curves_id_ = nullptr; | ||||
| CurvesGeometry *curves_ = nullptr; | CurvesGeometry *curves_ = nullptr; | ||||
| VArray<float> point_factors_; | VArray<float> point_factors_; | ||||
| Vector<int64_t> selected_curve_indices_; | Vector<int64_t> selected_curve_indices_; | ||||
| IndexMask curve_selection_; | IndexMask curve_selection_; | ||||
| Array<float3> start_positions_; | |||||
| const CurvesSculpt *curves_sculpt_ = nullptr; | const CurvesSculpt *curves_sculpt_ = nullptr; | ||||
| const Brush *brush_ = nullptr; | const Brush *brush_ = nullptr; | ||||
| float brush_radius_base_re_; | float brush_radius_base_re_; | ||||
| float brush_radius_factor_; | float brush_radius_factor_; | ||||
| float brush_strength_; | float brush_strength_; | ||||
| float2 brush_pos_re_; | float2 brush_pos_re_; | ||||
| ▲ Show 20 Lines • Show All 52 Lines • ▼ Show 20 Lines | void execute(PuffOperation &self, const bContext &C, const StrokeExtension &stroke_extension) | ||||
| BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2); | BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2); | ||||
| BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); }); | BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); }); | ||||
| surface_looptris_ = {BKE_mesh_runtime_looptri_ensure(surface_), | surface_looptris_ = {BKE_mesh_runtime_looptri_ensure(surface_), | ||||
| BKE_mesh_runtime_looptri_len(surface_)}; | BKE_mesh_runtime_looptri_len(surface_)}; | ||||
| if (stroke_extension.is_first) { | if (stroke_extension.is_first) { | ||||
| this->initialize_segment_lengths(); | |||||
| if (falloff_shape_ == PAINT_FALLOFF_SHAPE_SPHERE) { | if (falloff_shape_ == PAINT_FALLOFF_SHAPE_SPHERE) { | ||||
| self.brush_3d_ = *sample_curves_3d_brush(*ctx_.depsgraph, | self.brush_3d_ = *sample_curves_3d_brush(*ctx_.depsgraph, | ||||
| *ctx_.region, | *ctx_.region, | ||||
| *ctx_.v3d, | *ctx_.v3d, | ||||
| *ctx_.rv3d, | *ctx_.rv3d, | ||||
| *object_, | *object_, | ||||
| brush_pos_re_, | brush_pos_re_, | ||||
| brush_radius_base_re_); | brush_radius_base_re_); | ||||
| } | } | ||||
| ConstraintSolver::Params params; | |||||
| params.use_collision_constraints = curves_id_->flag & CV_SCULPT_COLLISION_ENABLED; | |||||
| self_->constraint_solver_.initialize(params, *curves_, curve_selection_); | |||||
| } | } | ||||
| start_positions_ = curves_->positions(); | |||||
| Array<float> curve_weights(curve_selection_.size(), 0.0f); | Array<float> curve_weights(curve_selection_.size(), 0.0f); | ||||
| if (falloff_shape_ == PAINT_FALLOFF_SHAPE_TUBE) { | if (falloff_shape_ == PAINT_FALLOFF_SHAPE_TUBE) { | ||||
| this->find_curve_weights_projected_with_symmetry(curve_weights); | this->find_curve_weights_projected_with_symmetry(curve_weights); | ||||
| } | } | ||||
| else if (falloff_shape_ == PAINT_FALLOFF_SHAPE_SPHERE) { | else if (falloff_shape_ == PAINT_FALLOFF_SHAPE_SPHERE) { | ||||
| this->find_curves_weights_spherical_with_symmetry(curve_weights); | this->find_curves_weights_spherical_with_symmetry(curve_weights); | ||||
| } | } | ||||
| else { | else { | ||||
| BLI_assert_unreachable(); | BLI_assert_unreachable(); | ||||
| } | } | ||||
| this->puff(curve_weights); | this->puff(curve_weights); | ||||
| this->restore_segment_lengths(); | |||||
| /* XXX Dumb array conversion to pass to the constraint solver. | |||||
| * Should become unnecessary once brushes use the same methods for computing weights */ | |||||
| Vector<int64_t> changed_curves_indices; | |||||
| changed_curves_indices.reserve(curve_selection_.size()); | |||||
| for (int64_t select_i : curve_selection_.index_range()) { | |||||
| if (curve_weights[select_i] > 0.0f) { | |||||
| changed_curves_indices.append(curve_selection_[select_i]); | |||||
| } | |||||
| } | |||||
| self_->constraint_solver_.step_curves(*curves_, | |||||
| surface_, | |||||
| transforms_, | |||||
| start_positions_, | |||||
| IndexMask(changed_curves_indices)); | |||||
| curves_->tag_positions_changed(); | curves_->tag_positions_changed(); | ||||
| DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); | DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); | ||||
| WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); | WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); | ||||
| ED_region_tag_redraw(ctx_.region); | ED_region_tag_redraw(ctx_.region); | ||||
| } | } | ||||
| void find_curve_weights_projected_with_symmetry(MutableSpan<float> r_curve_weights) | void find_curve_weights_projected_with_symmetry(MutableSpan<float> r_curve_weights) | ||||
| ▲ Show 20 Lines • Show All 164 Lines • ▼ Show 20 Lines | threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { | ||||
| new_pos_cu += (old_dist_to_root_cu - new_dist_to_root_cu) * offset; | new_pos_cu += (old_dist_to_root_cu - new_dist_to_root_cu) * offset; | ||||
| } | } | ||||
| positions_cu[point_i] = new_pos_cu; | positions_cu[point_i] = new_pos_cu; | ||||
| } | } | ||||
| } | } | ||||
| }); | }); | ||||
| } | } | ||||
| void initialize_segment_lengths() | |||||
| { | |||||
| const Span<float3> positions_cu = curves_->positions(); | |||||
| self_->segment_lengths_cu_.reinitialize(curves_->points_num()); | |||||
| threading::parallel_for(curves_->curves_range(), 128, [&](const IndexRange range) { | |||||
| for (const int curve_i : range) { | |||||
| const IndexRange points = curves_->points_for_curve(curve_i); | |||||
| for (const int point_i : points.drop_back(1)) { | |||||
| const float3 &p1_cu = positions_cu[point_i]; | |||||
| const float3 &p2_cu = positions_cu[point_i + 1]; | |||||
| const float length_cu = math::distance(p1_cu, p2_cu); | |||||
| self_->segment_lengths_cu_[point_i] = length_cu; | |||||
| } | |||||
| } | |||||
| }); | |||||
| } | |||||
| void restore_segment_lengths() | |||||
| { | |||||
| const Span<float> expected_lengths_cu = self_->segment_lengths_cu_; | |||||
| MutableSpan<float3> positions_cu = curves_->positions_for_write(); | |||||
| threading::parallel_for(curves_->curves_range(), 256, [&](const IndexRange range) { | |||||
| for (const int curve_i : range) { | |||||
| const IndexRange points = curves_->points_for_curve(curve_i); | |||||
| for (const int segment_i : points.drop_back(1)) { | |||||
| const float3 &p1_cu = positions_cu[segment_i]; | |||||
| float3 &p2_cu = positions_cu[segment_i + 1]; | |||||
| const float3 direction = math::normalize(p2_cu - p1_cu); | |||||
| const float expected_length_cu = expected_lengths_cu[segment_i]; | |||||
| p2_cu = p1_cu + direction * expected_length_cu; | |||||
| } | |||||
| } | |||||
| }); | |||||
| } | |||||
| }; | }; | ||||
| void PuffOperation::on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) | void PuffOperation::on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) | ||||
| { | { | ||||
| PuffOperationExecutor executor{C}; | PuffOperationExecutor executor{C}; | ||||
| executor.execute(*this, C, stroke_extension); | executor.execute(*this, C, stroke_extension); | ||||
| } | } | ||||
| std::unique_ptr<CurvesSculptStrokeOperation> new_puff_operation() | std::unique_ptr<CurvesSculptStrokeOperation> new_puff_operation() | ||||
| { | { | ||||
| return std::make_unique<PuffOperation>(); | return std::make_unique<PuffOperation>(); | ||||
| } | } | ||||
| } // namespace blender::ed::sculpt_paint | } // namespace blender::ed::sculpt_paint | ||||