Curves: initial surface collision for curves sculpt mode #104427
|
@ -161,6 +161,8 @@ class VIEW3D_HT_tool_header(Header):
|
|||
sub.prop(context.object.data, "use_mirror_y", text="Y", toggle=True)
|
||||
sub.prop(context.object.data, "use_mirror_z", text="Z", toggle=True)
|
||||
|
||||
layout.prop(context.object.data, "use_sculpt_collision", icon='MOD_PHYSICS', icon_only=True, toggle=True)
|
||||
|
||||
# Expand panels from the side-bar as popovers.
|
||||
popover_kw = {"space_type": 'VIEW_3D', "region_type": 'UI', "category": "Tool"}
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
|
||||
#include "BLT_translation.h"
|
||||
|
||||
#include "GEO_curve_constraint_solver.hh"
|
||||
|
||||
/**
|
||||
* The code below uses a prefix naming convention to indicate the coordinate space:
|
||||
* cu: Local space of the curves object that is being edited.
|
||||
|
@ -431,4 +433,40 @@ void report_invalid_uv_map(ReportList *reports)
|
|||
BKE_report(reports, RPT_WARNING, TIP_("Invalid UV map: UV islands must not overlap"));
|
||||
}
|
||||
|
||||
void CurvesConstraintSolver::initialize(const bke::CurvesGeometry &curves,
|
||||
const IndexMask curve_selection,
|
||||
const bool use_surface_collision)
|
||||
{
|
||||
use_surface_collision_ = use_surface_collision;
|
||||
segment_lengths_.reinitialize(curves.points_num());
|
||||
geometry::curve_constraint_solver::compute_segment_lengths(
|
||||
curves.points_by_curve(), curves.positions(), curve_selection, segment_lengths_);
|
||||
if (use_surface_collision_) {
|
||||
start_positions_ = curves.positions();
|
||||
}
|
||||
}
|
||||
|
||||
void CurvesConstraintSolver::solve_step(bke::CurvesGeometry &curves,
|
||||
const IndexMask curve_selection,
|
||||
const Mesh *surface,
|
||||
const CurvesSurfaceTransforms &transforms)
|
||||
{
|
||||
if (use_surface_collision_ && surface != nullptr) {
|
||||
geometry::curve_constraint_solver::solve_length_and_collision_constraints(
|
||||
curves.points_by_curve(),
|
||||
curve_selection,
|
||||
segment_lengths_,
|
||||
start_positions_,
|
||||
*surface,
|
||||
transforms,
|
||||
curves.positions_for_write());
|
||||
start_positions_ = curves.positions();
|
||||
}
|
||||
else {
|
||||
geometry::curve_constraint_solver::solve_length_constraints(
|
||||
curves.points_by_curve(), curve_selection, segment_lengths_, curves.positions_for_write());
|
||||
}
|
||||
curves.tag_positions_changed();
|
||||
}
|
||||
|
||||
} // namespace blender::ed::sculpt_paint
|
||||
|
|
|
@ -66,8 +66,8 @@ class CombOperation : public CurvesSculptStrokeOperation {
|
|||
/** Only used when a 3D brush is used. */
|
||||
CurvesBrush3D brush_3d_;
|
||||
|
||||
/** Length of each segment indexed by the index of the first point in the segment. */
|
||||
Array<float> segment_lengths_cu_;
|
||||
/** Solver for length and collision constraints. */
|
||||
CurvesConstraintSolver constraint_solver_;
|
||||
|
||||
friend struct CombOperationExecutor;
|
||||
|
||||
|
@ -98,6 +98,7 @@ struct CombOperationExecutor {
|
|||
VArray<float> point_factors_;
|
||||
Vector<int64_t> selected_curve_indices_;
|
||||
IndexMask curve_selection_;
|
||||
Array<float3> start_positions_;
|
||||
|
||||
float2 brush_pos_prev_re_;
|
||||
float2 brush_pos_re_;
|
||||
|
@ -144,12 +145,15 @@ struct CombOperationExecutor {
|
|||
if (falloff_shape_ == PAINT_FALLOFF_SHAPE_SPHERE) {
|
||||
this->initialize_spherical_brush_reference_point();
|
||||
}
|
||||
this->initialize_segment_lengths();
|
||||
self_->constraint_solver_.initialize(
|
||||
*curves_orig_, curve_selection_, curves_id_orig_->flag & CV_SCULPT_COLLISION_ENABLED);
|
||||
/* Combing does nothing when there is no mouse movement, so return directly. */
|
||||
return;
|
||||
}
|
||||
|
||||
EnumerableThreadSpecific<Vector<int>> changed_curves;
|
||||
start_positions_ = curves_orig_->positions();
|
||||
|
||||
EnumerableThreadSpecific<Vector<int64_t>> changed_curves;
|
||||
|
||||
if (falloff_shape_ == PAINT_FALLOFF_SHAPE_TUBE) {
|
||||
this->comb_projected_with_symmetry(changed_curves);
|
||||
|
@ -161,7 +165,25 @@ struct CombOperationExecutor {
|
|||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
this->restore_segment_lengths(changed_curves);
|
||||
const Mesh *surface = curves_id_orig_->surface && curves_id_orig_->surface->type == OB_MESH ?
|
||||
static_cast<Mesh *>(curves_id_orig_->surface->data) :
|
||||
nullptr;
|
||||
|
||||
/* Combine TLS curves into a single array for redistributing thread load.
|
||||
* Brush filtering results in TLS lists with potentially very uneven sizes. */
|
||||
int totcurves = 0;
|
||||
for (auto curves : changed_curves) {
|
||||
totcurves += curves.size();
|
||||
}
|
||||
Array<int64_t> all_changed_curves(totcurves);
|
||||
totcurves = 0;
|
||||
for (auto curves : changed_curves) {
|
||||
all_changed_curves.as_mutable_span().slice(totcurves, curves.size()).copy_from(curves);
|
||||
totcurves += curves.size();
|
||||
};
|
||||
|
||||
self_->constraint_solver_.solve_step(
|
||||
*curves_orig_, IndexMask(all_changed_curves), surface, transforms_);
|
||||
|
||||
curves_orig_->tag_positions_changed();
|
||||
DEG_id_tag_update(&curves_id_orig_->id, ID_RECALC_GEOMETRY);
|
||||
|
@ -172,7 +194,7 @@ struct CombOperationExecutor {
|
|||
/**
|
||||
* Do combing in screen space.
|
||||
*/
|
||||
void comb_projected_with_symmetry(EnumerableThreadSpecific<Vector<int>> &r_changed_curves)
|
||||
void comb_projected_with_symmetry(EnumerableThreadSpecific<Vector<int64_t>> &r_changed_curves)
|
||||
{
|
||||
const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
|
||||
eCurvesSymmetryType(curves_id_orig_->symmetry));
|
||||
|
@ -181,7 +203,7 @@ struct CombOperationExecutor {
|
|||
}
|
||||
}
|
||||
|
||||
void comb_projected(EnumerableThreadSpecific<Vector<int>> &r_changed_curves,
|
||||
void comb_projected(EnumerableThreadSpecific<Vector<int64_t>> &r_changed_curves,
|
||||
const float4x4 &brush_transform)
|
||||
{
|
||||
const float4x4 brush_transform_inv = math::invert(brush_transform);
|
||||
|
@ -198,7 +220,7 @@ struct CombOperationExecutor {
|
|||
const float brush_radius_sq_re = pow2f(brush_radius_re);
|
||||
|
||||
threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) {
|
||||
Vector<int> &local_changed_curves = r_changed_curves.local();
|
||||
Vector<int64_t> &local_changed_curves = r_changed_curves.local();
|
||||
for (const int curve_i : curve_selection_.slice(range)) {
|
||||
bool curve_changed = false;
|
||||
const IndexRange points = points_by_curve[curve_i];
|
||||
|
@ -255,7 +277,7 @@ struct CombOperationExecutor {
|
|||
/**
|
||||
* Do combing in 3D space.
|
||||
*/
|
||||
void comb_spherical_with_symmetry(EnumerableThreadSpecific<Vector<int>> &r_changed_curves)
|
||||
void comb_spherical_with_symmetry(EnumerableThreadSpecific<Vector<int64_t>> &r_changed_curves)
|
||||
{
|
||||
float4x4 projection;
|
||||
ED_view3d_ob_project_mat_get(ctx_.rv3d, curves_ob_orig_, projection.ptr());
|
||||
|
@ -289,7 +311,7 @@ struct CombOperationExecutor {
|
|||
}
|
||||
}
|
||||
|
||||
void comb_spherical(EnumerableThreadSpecific<Vector<int>> &r_changed_curves,
|
||||
void comb_spherical(EnumerableThreadSpecific<Vector<int64_t>> &r_changed_curves,
|
||||
const float3 &brush_start_cu,
|
||||
const float3 &brush_end_cu,
|
||||
const float brush_radius_cu)
|
||||
|
@ -303,7 +325,7 @@ struct CombOperationExecutor {
|
|||
const OffsetIndices points_by_curve = curves_orig_->points_by_curve();
|
||||
|
||||
threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) {
|
||||
Vector<int> &local_changed_curves = r_changed_curves.local();
|
||||
Vector<int64_t> &local_changed_curves = r_changed_curves.local();
|
||||
for (const int curve_i : curve_selection_.slice(range)) {
|
||||
bool curve_changed = false;
|
||||
const IndexRange points = points_by_curve[curve_i];
|
||||
|
@ -357,53 +379,6 @@ struct CombOperationExecutor {
|
|||
self_->brush_3d_ = *brush_3d;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remember the initial length of all curve segments. This allows restoring the length after
|
||||
* combing.
|
||||
*/
|
||||
void initialize_segment_lengths()
|
||||
{
|
||||
const Span<float3> positions_cu = curves_orig_->positions();
|
||||
const OffsetIndices points_by_curve = curves_orig_->points_by_curve();
|
||||
self_->segment_lengths_cu_.reinitialize(curves_orig_->points_num());
|
||||
threading::parallel_for(curves_orig_->curves_range(), 128, [&](const IndexRange range) {
|
||||
for (const int curve_i : range) {
|
||||
const IndexRange points = points_by_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;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore previously stored length for each segment in the changed curves.
|
||||
*/
|
||||
void restore_segment_lengths(EnumerableThreadSpecific<Vector<int>> &changed_curves)
|
||||
{
|
||||
const Span<float> expected_lengths_cu = self_->segment_lengths_cu_;
|
||||
const OffsetIndices points_by_curve = curves_orig_->points_by_curve();
|
||||
MutableSpan<float3> positions_cu = curves_orig_->positions_for_write();
|
||||
|
||||
threading::parallel_for_each(changed_curves, [&](const Vector<int> &changed_curves) {
|
||||
threading::parallel_for(changed_curves.index_range(), 256, [&](const IndexRange range) {
|
||||
for (const int curve_i : changed_curves.as_span().slice(range)) {
|
||||
const IndexRange points = points_by_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 CombOperation::on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension)
|
||||
|
|
|
@ -144,4 +144,25 @@ void report_missing_uv_map_on_original_surface(ReportList *reports);
|
|||
void report_missing_uv_map_on_evaluated_surface(ReportList *reports);
|
||||
void report_invalid_uv_map(ReportList *reports);
|
||||
|
||||
/**
|
||||
* Utility class to make it easy for brushes to implement length preservation and surface
|
||||
* collision.
|
||||
*/
|
||||
struct CurvesConstraintSolver {
|
||||
private:
|
||||
bool use_surface_collision_;
|
||||
Array<float3> start_positions_;
|
||||
Array<float> segment_lengths_;
|
||||
|
||||
public:
|
||||
void initialize(const bke::CurvesGeometry &curves,
|
||||
const IndexMask curve_selection,
|
||||
const bool use_surface_collision);
|
||||
|
||||
void solve_step(bke::CurvesGeometry &curves,
|
||||
const IndexMask curve_selection,
|
||||
const Mesh *surface,
|
||||
const CurvesSurfaceTransforms &transforms);
|
||||
};
|
||||
|
||||
} // namespace blender::ed::sculpt_paint
|
||||
|
|
|
@ -41,7 +41,9 @@ namespace blender::ed::sculpt_paint {
|
|||
class PinchOperation : public CurvesSculptStrokeOperation {
|
||||
private:
|
||||
bool invert_pinch_;
|
||||
Array<float> segment_lengths_cu_;
|
||||
|
||||
/** Solver for length and collision constraints. */
|
||||
CurvesConstraintSolver constraint_solver_;
|
||||
|
||||
/** Only used when a 3D brush is used. */
|
||||
CurvesBrush3D brush_3d_;
|
||||
|
@ -67,6 +69,7 @@ struct PinchOperationExecutor {
|
|||
VArray<float> point_factors_;
|
||||
Vector<int64_t> selected_curve_indices_;
|
||||
IndexMask curve_selection_;
|
||||
Array<float3> start_positions_;
|
||||
|
||||
CurvesSurfaceTransforms transforms_;
|
||||
|
||||
|
@ -114,8 +117,6 @@ struct PinchOperationExecutor {
|
|||
brush_->falloff_shape);
|
||||
|
||||
if (stroke_extension.is_first) {
|
||||
this->initialize_segment_lengths();
|
||||
|
||||
if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
|
||||
self_->brush_3d_ = *sample_curves_3d_brush(*ctx_.depsgraph,
|
||||
*ctx_.region,
|
||||
|
@ -125,8 +126,13 @@ struct PinchOperationExecutor {
|
|||
brush_pos_re_,
|
||||
brush_radius_base_re_);
|
||||
}
|
||||
|
||||
self_->constraint_solver_.initialize(
|
||||
*curves_, curve_selection_, curves_id_->flag & CV_SCULPT_COLLISION_ENABLED);
|
||||
}
|
||||
|
||||
start_positions_ = curves_->positions();
|
||||
|
||||
Array<bool> changed_curves(curves_->curves_num(), false);
|
||||
if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
|
||||
this->pinch_projected_with_symmetry(changed_curves);
|
||||
|
@ -138,7 +144,22 @@ struct PinchOperationExecutor {
|
|||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
this->restore_segment_lengths(changed_curves);
|
||||
/* 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(curves_->curves_num());
|
||||
for (int64_t curve_i : changed_curves.index_range()) {
|
||||
if (changed_curves[curve_i]) {
|
||||
changed_curves_indices.append(curve_i);
|
||||
}
|
||||
}
|
||||
|
||||
const Mesh *surface = curves_id_->surface && curves_id_->surface->type == OB_MESH ?
|
||||
static_cast<Mesh *>(curves_id_->surface->data) :
|
||||
nullptr;
|
||||
self_->constraint_solver_.solve_step(
|
||||
*curves_, IndexMask(changed_curves_indices), surface, transforms_);
|
||||
|
||||
curves_->tag_positions_changed();
|
||||
DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY);
|
||||
WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id);
|
||||
|
@ -269,47 +290,6 @@ struct PinchOperationExecutor {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
void initialize_segment_lengths()
|
||||
{
|
||||
const Span<float3> positions_cu = curves_->positions();
|
||||
const OffsetIndices points_by_curve = curves_->points_by_curve();
|
||||
self_->segment_lengths_cu_.reinitialize(curves_->points_num());
|
||||
threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) {
|
||||
for (const int curve_i : curve_selection_.slice(range)) {
|
||||
const IndexRange points = points_by_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<bool> changed_curves)
|
||||
{
|
||||
const Span<float> expected_lengths_cu = self_->segment_lengths_cu_;
|
||||
const OffsetIndices points_by_curve = curves_->points_by_curve();
|
||||
MutableSpan<float3> positions_cu = curves_->positions_for_write();
|
||||
|
||||
threading::parallel_for(changed_curves.index_range(), 256, [&](const IndexRange range) {
|
||||
for (const int curve_i : range) {
|
||||
if (!changed_curves[curve_i]) {
|
||||
continue;
|
||||
}
|
||||
const IndexRange points = points_by_curve[curve_i];
|
||||
for (const int segment_i : IndexRange(points.size() - 1)) {
|
||||
const float3 &p1_cu = positions_cu[points[segment_i]];
|
||||
float3 &p2_cu = positions_cu[points[segment_i] + 1];
|
||||
const float3 direction = math::normalize(p2_cu - p1_cu);
|
||||
const float expected_length_cu = expected_lengths_cu[points[segment_i]];
|
||||
p2_cu = p1_cu + direction * expected_length_cu;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
void PinchOperation::on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension)
|
||||
|
|
|
@ -33,8 +33,8 @@ class PuffOperation : public CurvesSculptStrokeOperation {
|
|||
/** Only used when a 3D brush is used. */
|
||||
CurvesBrush3D brush_3d_;
|
||||
|
||||
/** Length of each segment indexed by the index of the first point in the segment. */
|
||||
Array<float> segment_lengths_cu_;
|
||||
/** Solver for length and collision constraints. */
|
||||
CurvesConstraintSolver constraint_solver_;
|
||||
|
||||
friend struct PuffOperationExecutor;
|
||||
|
||||
|
@ -57,6 +57,7 @@ struct PuffOperationExecutor {
|
|||
VArray<float> point_factors_;
|
||||
Vector<int64_t> selected_curve_indices_;
|
||||
IndexMask curve_selection_;
|
||||
Array<float3> start_positions_;
|
||||
|
||||
const CurvesSculpt *curves_sculpt_ = nullptr;
|
||||
const Brush *brush_ = nullptr;
|
||||
|
@ -129,7 +130,6 @@ struct PuffOperationExecutor {
|
|||
BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); });
|
||||
|
||||
if (stroke_extension.is_first) {
|
||||
this->initialize_segment_lengths();
|
||||
if (falloff_shape_ == PAINT_FALLOFF_SHAPE_SPHERE) {
|
||||
self.brush_3d_ = *sample_curves_3d_brush(*ctx_.depsgraph,
|
||||
*ctx_.region,
|
||||
|
@ -139,8 +139,13 @@ struct PuffOperationExecutor {
|
|||
brush_pos_re_,
|
||||
brush_radius_base_re_);
|
||||
}
|
||||
|
||||
self_->constraint_solver_.initialize(
|
||||
*curves_, curve_selection_, curves_id_->flag & CV_SCULPT_COLLISION_ENABLED);
|
||||
}
|
||||
|
||||
start_positions_ = curves_->positions();
|
||||
|
||||
Array<float> curve_weights(curve_selection_.size(), 0.0f);
|
||||
|
||||
if (falloff_shape_ == PAINT_FALLOFF_SHAPE_TUBE) {
|
||||
|
@ -154,7 +159,19 @@ struct PuffOperationExecutor {
|
|||
}
|
||||
|
||||
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_.solve_step(
|
||||
*curves_, IndexMask(changed_curves_indices), surface_, transforms_);
|
||||
|
||||
curves_->tag_positions_changed();
|
||||
DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY);
|
||||
|
@ -343,44 +360,6 @@ struct PuffOperationExecutor {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
void initialize_segment_lengths()
|
||||
{
|
||||
const OffsetIndices points_by_curve = curves_->points_by_curve();
|
||||
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 = points_by_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_;
|
||||
const OffsetIndices points_by_curve = curves_->points_by_curve();
|
||||
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 = points_by_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)
|
||||
|
|
|
@ -16,6 +16,7 @@ set(INC
|
|||
|
||||
set(SRC
|
||||
intern/add_curves_on_mesh.cc
|
||||
intern/curve_constraint_solver.cc
|
||||
intern/fillet_curves.cc
|
||||
intern/mesh_merge_by_distance.cc
|
||||
intern/mesh_primitive_cuboid.cc
|
||||
|
@ -32,6 +33,7 @@ set(SRC
|
|||
intern/uv_parametrizer.cc
|
||||
|
||||
GEO_add_curves_on_mesh.hh
|
||||
GEO_curve_constraint_solver.hh
|
||||
GEO_fillet_curves.hh
|
||||
GEO_mesh_merge_by_distance.hh
|
||||
GEO_mesh_primitive_cuboid.hh
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_curves.hh"
|
||||
|
||||
namespace blender::geometry::curve_constraint_solver {
|
||||
|
||||
void compute_segment_lengths(OffsetIndices<int> points_by_curve,
|
||||
Span<float3> positions,
|
||||
IndexMask curve_selection,
|
||||
MutableSpan<float> r_segment_lengths);
|
||||
|
||||
void solve_length_constraints(OffsetIndices<int> points_by_curve,
|
||||
IndexMask curve_selection,
|
||||
Span<float> segment_lenghts,
|
||||
MutableSpan<float3> positions);
|
||||
|
||||
void solve_length_and_collision_constraints(OffsetIndices<int> points_by_curve,
|
||||
IndexMask curve_selection,
|
||||
Span<float> segment_lengths,
|
||||
Span<float3> start_positions,
|
||||
const Mesh &surface,
|
||||
const bke::CurvesSurfaceTransforms &transforms,
|
||||
MutableSpan<float3> positions);
|
||||
|
||||
} // namespace blender::geometry::curve_constraint_solver
|
|
@ -0,0 +1,160 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_matrix.hh"
|
||||
|
||||
#include "GEO_curve_constraint_solver.hh"
|
||||
|
||||
#include "BKE_bvhutils.h"
|
||||
|
||||
namespace blender::geometry::curve_constraint_solver {
|
||||
|
||||
void compute_segment_lengths(const OffsetIndices<int> points_by_curve,
|
||||
const Span<float3> positions,
|
||||
const IndexMask curve_selection,
|
||||
MutableSpan<float> r_segment_lengths)
|
||||
{
|
||||
BLI_assert(r_segment_lengths.size() == points_by_curve.total_size());
|
||||
|
||||
threading::parallel_for(curve_selection.index_range(), 256, [&](const IndexRange range) {
|
||||
for (const int curve_i : curve_selection.slice(range)) {
|
||||
const IndexRange points = points_by_curve[curve_i].drop_back(1);
|
||||
for (const int point_i : points) {
|
||||
const float3 &p1 = positions[point_i];
|
||||
const float3 &p2 = positions[point_i + 1];
|
||||
const float length = math::distance(p1, p2);
|
||||
r_segment_lengths[point_i] = length;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void solve_length_constraints(const OffsetIndices<int> points_by_curve,
|
||||
const IndexMask curve_selection,
|
||||
const Span<float> segment_lenghts,
|
||||
MutableSpan<float3> positions)
|
||||
{
|
||||
BLI_assert(segment_lenghts.size() == points_by_curve.total_size());
|
||||
|
||||
threading::parallel_for(curve_selection.index_range(), 256, [&](const IndexRange range) {
|
||||
for (const int curve_i : curve_selection.slice(range)) {
|
||||
const IndexRange points = points_by_curve[curve_i].drop_back(1);
|
||||
for (const int point_i : points) {
|
||||
const float3 &p1 = positions[point_i];
|
||||
float3 &p2 = positions[point_i + 1];
|
||||
const float3 direction = math::normalize(p2 - p1);
|
||||
const float goal_length = segment_lenghts[point_i];
|
||||
p2 = p1 + direction * goal_length;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void solve_length_and_collision_constraints(const OffsetIndices<int> points_by_curve,
|
||||
const IndexMask curve_selection,
|
||||
const Span<float> segment_lengths_cu,
|
||||
const Span<float3> start_positions_cu,
|
||||
const Mesh &surface,
|
||||
const bke::CurvesSurfaceTransforms &transforms,
|
||||
MutableSpan<float3> positions_cu)
|
||||
{
|
||||
solve_length_constraints(points_by_curve, curve_selection, segment_lengths_cu, positions_cu);
|
||||
|
||||
BVHTreeFromMesh surface_bvh;
|
||||
BKE_bvhtree_from_mesh_get(&surface_bvh, &surface, BVHTREE_FROM_LOOPTRI, 2);
|
||||
BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh); });
|
||||
|
||||
const float radius = 0.001f;
|
||||
const int max_collisions = 5;
|
||||
|
||||
threading::parallel_for(curve_selection.index_range(), 64, [&](const IndexRange range) {
|
||||
for (const int curve_i : curve_selection.slice(range)) {
|
||||
const IndexRange points = points_by_curve[curve_i];
|
||||
|
||||
/* Sometimes not all collisions can be handled. This happens relatively rarely, but if it
|
||||
* happens it's better to just not to move the curve instead of going into the surface. */
|
||||
bool revert_curve = false;
|
||||
for (const int point_i : points.drop_front(1)) {
|
||||
const float goal_segment_length_cu = segment_lengths_cu[point_i - 1];
|
||||
const float3 &prev_pos_cu = positions_cu[point_i - 1];
|
||||
const float3 &start_pos_cu = start_positions_cu[point_i];
|
||||
|
||||
int used_iterations = 0;
|
||||
for ([[maybe_unused]] const int iteration : IndexRange(max_collisions)) {
|
||||
used_iterations++;
|
||||
const float3 &old_pos_cu = positions_cu[point_i];
|
||||
if (start_pos_cu == old_pos_cu) {
|
||||
/* The point did not move, done. */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Check if the point moved through a surface. */
|
||||
const float3 start_pos_su = math::transform_point(transforms.curves_to_surface,
|
||||
start_pos_cu);
|
||||
const float3 old_pos_su = math::transform_point(transforms.curves_to_surface,
|
||||
old_pos_cu);
|
||||
const float3 pos_diff_su = old_pos_su - start_pos_su;
|
||||
float max_ray_length_su;
|
||||
const float3 ray_direction_su = math::normalize_and_get_length(pos_diff_su,
|
||||
max_ray_length_su);
|
||||
BVHTreeRayHit hit;
|
||||
hit.index = -1;
|
||||
hit.dist = max_ray_length_su + radius;
|
||||
BLI_bvhtree_ray_cast(surface_bvh.tree,
|
||||
start_pos_su,
|
||||
ray_direction_su,
|
||||
radius,
|
||||
&hit,
|
||||
surface_bvh.raycast_callback,
|
||||
&surface_bvh);
|
||||
if (hit.index == -1) {
|
||||
break;
|
||||
}
|
||||
const float3 hit_pos_su = hit.co;
|
||||
const float3 hit_normal_su = hit.no;
|
||||
if (math::dot(hit_normal_su, ray_direction_su) > 0.0f) {
|
||||
/* Moving from the inside to the outside is ok. */
|
||||
break;
|
||||
}
|
||||
|
||||
/* The point was moved through a surface. Now put it back on the correct side of the
|
||||
* surface and slide it on the surface to keep the length the same. */
|
||||
|
||||
const float3 hit_pos_cu = math::transform_point(transforms.surface_to_curves,
|
||||
hit_pos_su);
|
||||
const float3 hit_normal_cu = math::normalize(
|
||||
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_normal_cu = hit_normal_cu;
|
||||
|
||||
/* Decompose the current segment into the part normal and tangent to the collision
|
||||
* surface. */
|
||||
const float3 collided_segment_cu = plane_pos_cu - prev_pos_cu;
|
||||
const float3 slide_normal_cu = plane_normal_cu *
|
||||
math::dot(collided_segment_cu, plane_normal_cu);
|
||||
const float3 slide_direction_cu = collided_segment_cu - slide_normal_cu;
|
||||
|
||||
float slide_direction_length_cu;
|
||||
const float3 normalized_slide_direction_cu = math::normalize_and_get_length(
|
||||
slide_direction_cu, slide_direction_length_cu);
|
||||
|
||||
/* Use pythagorian theory to determine how far to slide. */
|
||||
const float slide_distance_cu = std::sqrt(pow2f(goal_segment_length_cu) -
|
||||
math::length_squared(slide_normal_cu)) -
|
||||
slide_direction_length_cu;
|
||||
positions_cu[point_i] = plane_pos_cu + normalized_slide_direction_cu * slide_distance_cu;
|
||||
}
|
||||
if (used_iterations == max_collisions) {
|
||||
revert_curve = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (revert_curve) {
|
||||
positions_cu.slice(points).copy_from(start_positions_cu.slice(points));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace blender::geometry::curve_constraint_solver
|
|
@ -197,6 +197,7 @@ typedef struct Curves {
|
|||
/** #Curves.flag */
|
||||
enum {
|
||||
HA_DS_EXPAND = (1 << 0),
|
||||
CV_SCULPT_COLLISION_ENABLED = (1 << 1),
|
||||
};
|
||||
|
||||
/** #Curves.symmetry */
|
||||
|
|
|
@ -453,6 +453,13 @@ static void rna_def_curves(BlenderRNA *brna)
|
|||
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
|
||||
RNA_def_property_update(prop, 0, "rna_Curves_update_data");
|
||||
|
||||
prop = RNA_def_property(srna, "use_sculpt_collision", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, NULL, "flag", CV_SCULPT_COLLISION_ENABLED);
|
||||
RNA_def_property_ui_text(
|
||||
prop, "Use Sculpt Collision", "Enable collision handling with the surface during sculpting");
|
||||
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
|
||||
RNA_def_property_update(prop, 0, "rna_Curves_update_draw");
|
||||
|
||||
/* attributes */
|
||||
rna_def_attributes_common(srna);
|
||||
|
||||
|
|
Loading…
Reference in New Issue