From 47d961a4b1c14b0cee2817de226ee356e711e146 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 15 Apr 2022 09:54:15 -0500 Subject: Fix: Apply tilt in curves data-block normals calculation The ported normal calculation from ceed37fc5cbb466a0 neglected to use the tilt attribute to rotate the normals around the tangents. This commit adds that behavior back, adding a new math header file to avoid duplicating the rotation function for normalized axes. Differential Revision: https://developer.blender.org/D14655 --- source/blender/blenkernel/BKE_curves.hh | 7 ++++ source/blender/blenkernel/intern/curve_poly.cc | 20 ++-------- .../blender/blenkernel/intern/curves_geometry.cc | 46 ++++++++++++++++++++++ source/blender/blenlib/BLI_math_rotation.hh | 18 +++++++++ source/blender/blenlib/CMakeLists.txt | 2 + source/blender/blenlib/intern/math_rotation.cc | 26 ++++++++++++ .../blenlib/tests/BLI_math_rotation_test.cc | 22 +++++++++++ 7 files changed, 124 insertions(+), 17 deletions(-) create mode 100644 source/blender/blenlib/BLI_math_rotation.hh create mode 100644 source/blender/blenlib/intern/math_rotation.cc diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh index 06971a2243a..3d912e10fb2 100644 --- a/source/blender/blenkernel/BKE_curves.hh +++ b/source/blender/blenkernel/BKE_curves.hh @@ -182,6 +182,13 @@ class CurvesGeometry : public ::CurvesGeometry { /** Mutable access to curve resolution. Call #tag_topology_changed after changes. */ MutableSpan resolution_for_write(); + /** + * The angle used to rotate evaluated normals around the tangents after their calculation. + * Call #tag_normals_changed after changes. + */ + VArray tilt() const; + MutableSpan tilt_for_write(); + /** * Which method to use for calculating the normals of evaluated points (#NormalMode). * Call #tag_normals_changed after changes. diff --git a/source/blender/blenkernel/intern/curve_poly.cc b/source/blender/blenkernel/intern/curve_poly.cc index b0ed62d38dd..2db7cd71ad3 100644 --- a/source/blender/blenkernel/intern/curve_poly.cc +++ b/source/blender/blenkernel/intern/curve_poly.cc @@ -6,7 +6,7 @@ #include -#include "BLI_math_vector.h" +#include "BLI_math_rotation.hh" #include "BLI_math_vector.hh" #include "BKE_curves.hh" @@ -54,20 +54,6 @@ void calculate_tangents(const Span positions, } } -static float3 rotate_direction_around_axis(const float3 &direction, - const float3 &axis, - const float angle) -{ - BLI_ASSERT_UNIT_V3(direction); - BLI_ASSERT_UNIT_V3(axis); - - const float3 axis_scaled = axis * math::dot(direction, axis); - const float3 diff = direction - axis_scaled; - const float3 cross = math::cross(axis, diff); - - return axis_scaled + diff * std::cos(angle) + cross * std::sin(angle); -} - void calculate_normals_z_up(const Span tangents, MutableSpan normals) { BLI_assert(normals.size() == tangents.size()); @@ -98,7 +84,7 @@ static float3 calculate_next_normal(const float3 &last_normal, const float angle = angle_normalized_v3v3(last_tangent, current_tangent); if (angle != 0.0) { const float3 axis = math::normalize(math::cross(last_tangent, current_tangent)); - return rotate_direction_around_axis(last_normal, axis, angle); + return math::rotate_direction_around_axis(last_normal, axis, angle); } return last_normal; } @@ -147,7 +133,7 @@ void calculate_normals_minimum(const Span tangents, const float angle_step = correction_angle / normals.size(); for (const int i : normals.index_range()) { const float angle = angle_step * i; - normals[i] = rotate_direction_around_axis(normals[i], tangents[i], angle); + normals[i] = math::rotate_direction_around_axis(normals[i], tangents[i], angle); } } diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc index bdd8b3fc3d0..24e156c3c4d 100644 --- a/source/blender/blenkernel/intern/curves_geometry.cc +++ b/source/blender/blenkernel/intern/curves_geometry.cc @@ -12,6 +12,7 @@ #include "BLI_bounds.hh" #include "BLI_index_mask_ops.hh" #include "BLI_length_parameterize.hh" +#include "BLI_math_rotation.hh" #include "DNA_curves_types.h" @@ -22,6 +23,7 @@ namespace blender::bke { static const std::string ATTR_POSITION = "position"; static const std::string ATTR_RADIUS = "radius"; +static const std::string ATTR_TILT = "tilt"; static const std::string ATTR_CURVE_TYPE = "curve_type"; static const std::string ATTR_CYCLIC = "cyclic"; static const std::string ATTR_RESOLUTION = "resolution"; @@ -330,6 +332,15 @@ MutableSpan CurvesGeometry::normal_mode_for_write() return get_mutable_attribute(*this, ATTR_DOMAIN_CURVE, ATTR_NORMAL_MODE); } +VArray CurvesGeometry::tilt() const +{ + return get_varray_attribute(*this, ATTR_DOMAIN_POINT, ATTR_TILT, 0.0f); +} +MutableSpan CurvesGeometry::tilt_for_write() +{ + return get_mutable_attribute(*this, ATTR_DOMAIN_POINT, ATTR_TILT); +} + VArray CurvesGeometry::handle_types_left() const { return get_varray_attribute(*this, ATTR_DOMAIN_POINT, ATTR_HANDLE_TYPE_LEFT, 0); @@ -717,6 +728,15 @@ Span CurvesGeometry::evaluated_tangents() const return this->runtime->evaluated_tangent_cache; } +static void rotate_directions_around_axes(MutableSpan directions, + const Span axes, + const Span angles) +{ + for (const int i : directions.index_range()) { + directions[i] = math::rotate_direction_around_axis(directions[i], axes[i], angles[i]); + } +} + Span CurvesGeometry::evaluated_normals() const { if (!this->runtime->normal_cache_dirty) { @@ -733,11 +753,16 @@ Span CurvesGeometry::evaluated_normals() const const Span evaluated_tangents = this->evaluated_tangents(); const VArray cyclic = this->cyclic(); const VArray normal_mode = this->normal_mode(); + const VArray types = this->curve_types(); + const VArray tilt = this->tilt(); this->runtime->evaluated_normal_cache.resize(this->evaluated_points_num()); MutableSpan evaluated_normals = this->runtime->evaluated_normal_cache; threading::parallel_for(this->curves_range(), 128, [&](IndexRange curves_range) { + /* Reuse a buffer for the evaluated tilts. */ + Vector evaluated_tilts; + for (const int curve_index : curves_range) { const IndexRange evaluated_points = this->evaluated_points_for_curve(curve_index); if (UNLIKELY(evaluated_points.is_empty())) { @@ -754,6 +779,27 @@ Span CurvesGeometry::evaluated_normals() const evaluated_normals.slice(evaluated_points)); break; } + + /* If the "tilt" attribute exists, rotate the normals around the tangents by the + * evaluated angles. We can avoid copying the tilts to evaluate them for poly curves. */ + if (!(tilt.is_single() && tilt.get_internal_single() == 0.0f)) { + const IndexRange points = this->points_for_curve(curve_index); + Span curve_tilt = tilt.get_internal_span().slice(points); + if (types[curve_index] == CURVE_TYPE_POLY) { + rotate_directions_around_axes(evaluated_normals.slice(evaluated_points), + evaluated_tangents.slice(evaluated_points), + curve_tilt); + } + else { + evaluated_tilts.clear(); + evaluated_tilts.resize(evaluated_points.size()); + this->interpolate_to_evaluated( + curve_index, curve_tilt, evaluated_tilts.as_mutable_span()); + rotate_directions_around_axes(evaluated_normals.slice(evaluated_points), + evaluated_tangents.slice(evaluated_points), + evaluated_tilts.as_span()); + } + } } }); }); diff --git a/source/blender/blenlib/BLI_math_rotation.hh b/source/blender/blenlib/BLI_math_rotation.hh new file mode 100644 index 00000000000..e8b746b34df --- /dev/null +++ b/source/blender/blenlib/BLI_math_rotation.hh @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup bli + */ + +#include "BLI_math_vec_types.hh" + +namespace blender::math { + +/** + * Rotate the unit-length \a direction around the unit-length \a axis by the \a angle. + */ +float3 rotate_direction_around_axis(const float3 &direction, const float3 &axis, float angle); + +} // namespace blender::math diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index 99e07264276..e8a3851e082 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -94,6 +94,7 @@ set(SRC intern/math_interp.c intern/math_matrix.c intern/math_rotation.c + intern/math_rotation.cc intern/math_solvers.c intern/math_statistics.c intern/math_time.c @@ -251,6 +252,7 @@ set(SRC BLI_math_matrix.h BLI_math_mpq.hh BLI_math_rotation.h + BLI_math_rotation.hh BLI_math_solvers.h BLI_math_statistics.h BLI_math_time.h diff --git a/source/blender/blenlib/intern/math_rotation.cc b/source/blender/blenlib/intern/math_rotation.cc new file mode 100644 index 00000000000..74300d55954 --- /dev/null +++ b/source/blender/blenlib/intern/math_rotation.cc @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup bli + */ + +#include "BLI_math_base.h" +#include "BLI_math_rotation.hh" +#include "BLI_math_vector.h" +#include "BLI_math_vector.hh" + +namespace blender::math { + +float3 rotate_direction_around_axis(const float3 &direction, const float3 &axis, const float angle) +{ + BLI_ASSERT_UNIT_V3(direction); + BLI_ASSERT_UNIT_V3(axis); + + const float3 axis_scaled = axis * math::dot(direction, axis); + const float3 diff = direction - axis_scaled; + const float3 cross = math::cross(axis, diff); + + return axis_scaled + diff * std::cos(angle) + cross * std::sin(angle); +} + +} // namespace blender::math diff --git a/source/blender/blenlib/tests/BLI_math_rotation_test.cc b/source/blender/blenlib/tests/BLI_math_rotation_test.cc index a10e441cfbe..a283118bea2 100644 --- a/source/blender/blenlib/tests/BLI_math_rotation_test.cc +++ b/source/blender/blenlib/tests/BLI_math_rotation_test.cc @@ -4,6 +4,8 @@ #include "BLI_math_base.h" #include "BLI_math_rotation.h" +#include "BLI_math_rotation.hh" +#include "BLI_math_vector.hh" #include @@ -147,3 +149,23 @@ TEST(math_rotation, quat_split_swing_and_twist_negative) EXPECT_V4_NEAR(swing, expected_swing, FLT_EPSILON); EXPECT_V4_NEAR(twist, expected_twist, FLT_EPSILON); } + +namespace blender::math::tests { + +TEST(math_rotation, RotateDirectionAroundAxis) +{ + const float3 a = rotate_direction_around_axis({1, 0, 0}, {0, 0, 1}, M_PI_2); + EXPECT_NEAR(a.x, 0.0f, FLT_EPSILON); + EXPECT_NEAR(a.y, 1.0f, FLT_EPSILON); + EXPECT_NEAR(a.z, 0.0f, FLT_EPSILON); + const float3 b = rotate_direction_around_axis({1, 0, 0}, {0, 0, 1}, M_PI); + EXPECT_NEAR(b.x, -1.0f, FLT_EPSILON); + EXPECT_NEAR(b.y, 0.0f, FLT_EPSILON); + EXPECT_NEAR(b.z, 0.0f, FLT_EPSILON); + const float3 c = rotate_direction_around_axis({0, 0, 1}, {0, 0, 1}, 0.0f); + EXPECT_NEAR(c.x, 0.0f, FLT_EPSILON); + EXPECT_NEAR(c.y, 0.0f, FLT_EPSILON); + EXPECT_NEAR(c.z, 1.0f, FLT_EPSILON); +} + +} // namespace blender::math::tests -- cgit v1.2.3