diff options
Diffstat (limited to 'source/blender/blenkernel/intern/spline_base.cc')
-rw-r--r-- | source/blender/blenkernel/intern/spline_base.cc | 184 |
1 files changed, 164 insertions, 20 deletions
diff --git a/source/blender/blenkernel/intern/spline_base.cc b/source/blender/blenkernel/intern/spline_base.cc index 8956ba6adae..584156ea40f 100644 --- a/source/blender/blenkernel/intern/spline_base.cc +++ b/source/blender/blenkernel/intern/spline_base.cc @@ -40,6 +40,60 @@ Spline::Type Spline::type() const return type_; } +void Spline::copy_base_settings(const Spline &src, Spline &dst) +{ + dst.normal_mode = src.normal_mode; + dst.is_cyclic_ = src.is_cyclic_; +} + +static SplinePtr create_spline(const Spline::Type type) +{ + switch (type) { + case Spline::Type::Poly: + return std::make_unique<PolySpline>(); + case Spline::Type::Bezier: + return std::make_unique<BezierSpline>(); + case Spline::Type::NURBS: + return std::make_unique<NURBSpline>(); + } + BLI_assert_unreachable(); + return {}; +} + +/** + * Return a new spline with the same data, settings, and attributes. + */ +SplinePtr Spline::copy() const +{ + SplinePtr dst = this->copy_without_attributes(); + dst->attributes = this->attributes; + return dst; +} + +/** + * Return a new spline with the same type and settings like "cyclic", but without any data. + */ +SplinePtr Spline::copy_only_settings() const +{ + SplinePtr dst = create_spline(type_); + this->copy_base_settings(*this, *dst); + this->copy_settings(*dst); + return dst; +} + +/** + * The same as #copy, but skips copying dynamic attributes to the new spline. + */ +SplinePtr Spline::copy_without_attributes() const +{ + SplinePtr dst = this->copy_only_settings(); + this->copy_data(*dst); + + /* Though the attributes storage is empty, it still needs to know the correct size. */ + dst->attributes.reallocate(dst->size()); + return dst; +} + void Spline::translate(const blender::float3 &translation) { for (float3 &position : this->positions()) { @@ -74,9 +128,9 @@ float Spline::length() const int Spline::segments_size() const { - const int points_len = this->size(); + const int size = this->size(); - return is_cyclic_ ? points_len : points_len - 1; + return is_cyclic_ ? size : size - 1; } bool Spline::is_cyclic() const @@ -209,10 +263,86 @@ static float3 rotate_direction_around_axis(const float3 &direction, return axis_scaled + diff * std::cos(angle) + cross * std::sin(angle); } -static void calculate_normals_z_up(Span<float3> tangents, MutableSpan<float3> normals) +static void calculate_normals_z_up(Span<float3> tangents, MutableSpan<float3> r_normals) { - for (const int i : normals.index_range()) { - normals[i] = float3::cross(tangents[i], float3(0.0f, 0.0f, 1.0f)).normalized(); + BLI_assert(r_normals.size() == tangents.size()); + + /* Same as in `vec_to_quat`. */ + const float epsilon = 1e-4f; + for (const int i : r_normals.index_range()) { + const float3 &tangent = tangents[i]; + if (fabsf(tangent.x) + fabsf(tangent.y) < epsilon) { + r_normals[i] = {1.0f, 0.0f, 0.0f}; + } + else { + r_normals[i] = float3(tangent.y, -tangent.x, 0.0f).normalized(); + } + } +} + +/** + * Rotate the last normal in the same way the tangent has been rotated. + */ +static float3 calculate_next_normal(const float3 &last_normal, + const float3 &last_tangent, + const float3 ¤t_tangent) +{ + if (last_tangent.is_zero() || current_tangent.is_zero()) { + return last_normal; + } + const float angle = angle_normalized_v3v3(last_tangent, current_tangent); + if (angle != 0.0) { + const float3 axis = float3::cross(last_tangent, current_tangent).normalized(); + return rotate_direction_around_axis(last_normal, axis, angle); + } + return last_normal; +} + +static void calculate_normals_minimum(Span<float3> tangents, + const bool cyclic, + MutableSpan<float3> r_normals) +{ + BLI_assert(r_normals.size() == tangents.size()); + + if (r_normals.is_empty()) { + return; + } + + const float epsilon = 1e-4f; + + /* Set initial normal. */ + const float3 &first_tangent = tangents[0]; + if (fabs(first_tangent.x) + fabs(first_tangent.y) < epsilon) { + r_normals[0] = {1.0f, 0.0f, 0.0f}; + } + else { + r_normals[0] = float3(first_tangent.y, -first_tangent.x, 0.0f).normalized(); + } + + /* Forward normal with minimum twist along the entire spline. */ + for (const int i : IndexRange(1, r_normals.size() - 1)) { + r_normals[i] = calculate_next_normal(r_normals[i - 1], tangents[i - 1], tangents[i]); + } + + if (!cyclic) { + return; + } + + /* Compute how much the first normal deviates from the normal that has been forwarded along the + * entire cyclic spline. */ + const float3 uncorrected_last_normal = calculate_next_normal( + r_normals.last(), tangents.last(), tangents[0]); + float correction_angle = angle_signed_on_axis_v3v3_v3( + r_normals[0], uncorrected_last_normal, tangents[0]); + if (correction_angle > M_PI) { + correction_angle = correction_angle - 2 * M_PI; + } + + /* Gradually apply correction by rotating all normals slightly. */ + const float angle_step = correction_angle / r_normals.size(); + for (const int i : r_normals.index_range()) { + const float angle = angle_step * i; + r_normals[i] = rotate_direction_around_axis(r_normals[i], tangents[i], angle); } } @@ -234,14 +364,28 @@ Span<float3> Spline::evaluated_normals() const const int eval_size = this->evaluated_points_size(); evaluated_normals_cache_.resize(eval_size); - Span<float3> tangents = evaluated_tangents(); + Span<float3> tangents = this->evaluated_tangents(); MutableSpan<float3> normals = evaluated_normals_cache_; /* Only Z up normals are supported at the moment. */ - calculate_normals_z_up(tangents, normals); + switch (this->normal_mode) { + case ZUp: { + calculate_normals_z_up(tangents, normals); + break; + } + case Minimum: { + calculate_normals_minimum(tangents, is_cyclic_, normals); + break; + } + case Tangent: { + /* Tangent mode is not yet supported. */ + calculate_normals_z_up(tangents, normals); + break; + } + } /* Rotate the generated normals with the interpolated tilt data. */ - GVArray_Typed<float> tilts = this->interpolate_to_evaluated_points(this->tilts()); + GVArray_Typed<float> tilts = this->interpolate_to_evaluated(this->tilts()); for (const int i : normals.index_range()) { normals[i] = rotate_direction_around_axis(normals[i], tangents[i], tilts[i]); } @@ -380,23 +524,23 @@ void Spline::sample_length_parameters_to_index_factors(MutableSpan<float> parame Spline::LookupResult Spline::lookup_data_from_index_factor(const float index_factor) const { - const int points_len = this->evaluated_points_size(); + const int eval_size = this->evaluated_points_size(); if (is_cyclic_) { - if (index_factor < points_len) { + if (index_factor < eval_size) { const int index = std::floor(index_factor); - const int next_index = (index < points_len - 1) ? index + 1 : 0; + const int next_index = (index < eval_size - 1) ? index + 1 : 0; return LookupResult{index, next_index, index_factor - index}; } - return LookupResult{points_len - 1, 0, 1.0f}; + return LookupResult{eval_size - 1, 0, 1.0f}; } - if (index_factor < points_len - 1) { + if (index_factor < eval_size - 1) { const int index = std::floor(index_factor); const int next_index = index + 1; return LookupResult{index, next_index, index_factor - index}; } - return LookupResult{points_len - 2, points_len - 1, 1.0f}; + return LookupResult{eval_size - 2, eval_size - 1, 1.0f}; } void Spline::bounds_min_max(float3 &min, float3 &max, const bool use_evaluated) const @@ -407,9 +551,9 @@ void Spline::bounds_min_max(float3 &min, float3 &max, const bool use_evaluated) } } -GVArrayPtr Spline::interpolate_to_evaluated_points(GSpan data) const +GVArrayPtr Spline::interpolate_to_evaluated(GSpan data) const { - return this->interpolate_to_evaluated_points(GVArray_For_GSpan(data)); + return this->interpolate_to_evaluated(GVArray_For_GSpan(data)); } /** @@ -417,9 +561,9 @@ GVArrayPtr Spline::interpolate_to_evaluated_points(GSpan data) const * points) to arbitrary parameters in between the evaluated points. The interpolation is quite * simple, but this handles the cyclic and end point special cases. */ -void Spline::sample_based_on_index_factors(const GVArray &src, - Span<float> index_factors, - GMutableSpan dst) const +void Spline::sample_with_index_factors(const GVArray &src, + Span<float> index_factors, + GMutableSpan dst) const { BLI_assert(src.size() == this->evaluated_points_size()); @@ -427,7 +571,7 @@ void Spline::sample_based_on_index_factors(const GVArray &src, using T = decltype(dummy); const GVArray_Typed<T> src_typed = src.typed<T>(); MutableSpan<T> dst_typed = dst.typed<T>(); - blender::parallel_for(dst_typed.index_range(), 1024, [&](IndexRange range) { + blender::threading::parallel_for(dst_typed.index_range(), 1024, [&](IndexRange range) { for (const int i : range) { const LookupResult interp = this->lookup_data_from_index_factor(index_factors[i]); dst_typed[i] = blender::attribute_math::mix2(interp.factor, |