Skip to content

Commit

Permalink
[Math/Vector] Linear interpolation now works properly for integer types
Browse files Browse the repository at this point in the history
- Prior to this, the coefficient was implicitly converted to an integer, breaking the computation

- lerp() has a templated type for its return value
  - This can avoid truncating the resulting values if needed by allowing to give a specific type other than the original one

- Both lerp() & nlerp() have a templated type for the coefficient

- Like normalize(), nlerp() uses a float vector as a result for integer vectors, in order to avoid truncation
  • Loading branch information
Razakhel committed Sep 22, 2023
1 parent 87234f0 commit 80454bb
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 11 deletions.
11 changes: 9 additions & 2 deletions include/RaZ/Math/Vector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,22 @@ class Vector {
template <typename NormedT = std::conditional_t<std::is_integral_v<T>, float, T>>
constexpr Vector<NormedT, Size> normalize() const noexcept;
/// Computes the linear interpolation between vectors, according to a coefficient.
/// \tparam LerpT Type of the interpolated vector's values. By default, it is the same as the current vector's.
/// \tparam CoeffT Type of the coefficient. For vectors of an integral type, it is defined to float; otherwise, it is the same as the original vectors'.
/// \param vec Vector to be interpolated with.
/// \param coeff Coefficient between 0 (returns the current vector) and 1 (returns the given vector).
/// \return Linearly interpolated vector.
constexpr Vector lerp(const Vector& vec, float coeff) const noexcept;
template <typename LerpT = T, typename CoeffT = std::conditional_t<std::is_integral_v<T>, float, T>>
constexpr Vector<LerpT, Size> lerp(const Vector& vec, CoeffT coeff) const noexcept;
/// Computes the normalized linear interpolation between vectors, according to a coefficient.
/// \tparam NormedT Type of the normalized interpolated vector's values. For vectors of an integral type, it is defined to float;
/// otherwise, it is the same as the original vectors'.
/// \tparam CoeffT Type of the coefficient. For vectors of an integral type, it is defined to float; otherwise, it is the same as the original vectors'.
/// \param vec Vector to be interpolated with.
/// \param coeff Coefficient between 0 (returns the normalized current vector) and 1 (returns the normalized given vector).
/// \return Normalized linearly interpolated vector.
constexpr Vector nlerp(const Vector& vec, float coeff) const noexcept { return lerp(vec, coeff).normalize(); }
template <typename NormedT = std::conditional_t<std::is_integral_v<T>, float, T>, typename CoeffT = std::conditional_t<std::is_integral_v<T>, float, T>>
constexpr Vector<NormedT, Size> nlerp(const Vector& vec, CoeffT coeff) const noexcept { return lerp<NormedT>(vec, coeff).normalize(); }
/// Checks for strict equality between the current vector & the given one.
/// \param vec Vector to be compared with.
/// \return True if vectors are strictly equal to each other, false otherwise.
Expand Down
10 changes: 7 additions & 3 deletions include/RaZ/Math/Vector.inl
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,19 @@ constexpr Vector<NormedT, Size> Vector<T, Size>::normalize() const noexcept {
}

template <typename T, std::size_t Size>
constexpr Vector<T, Size> Vector<T, Size>::lerp(const Vector& vec, float coeff) const noexcept {
template <typename LerpT, typename CoeffT>
constexpr Vector<LerpT, Size> Vector<T, Size>::lerp(const Vector& vec, CoeffT coeff) const noexcept {
static_assert(std::is_floating_point_v<CoeffT>, "Error: The linear interpolation's coefficient type must be floating-point.");
assert("Error: The interpolation coefficient must be between 0 & 1." && (coeff >= 0 && coeff <= 1));

return *this + (vec - *this) * coeff;
const Vector<CoeffT, Size> convertedThis(*this);
const Vector<CoeffT, Size> lerpVec = convertedThis + (Vector<CoeffT, Size>(vec) - convertedThis) * coeff;
return Vector<LerpT, Size>(lerpVec);
}

template <typename T, std::size_t Size>
constexpr bool Vector<T, Size>::strictlyEquals(const Vector& vec) const noexcept {
return std::equal(m_data.cbegin(), m_data.cend(), vec.getData().cbegin());
return std::equal(m_data.cbegin(), m_data.cend(), vec.m_data.cbegin());
}

template <typename T, std::size_t Size>
Expand Down
12 changes: 6 additions & 6 deletions src/RaZ/Script/LuaVector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ void LuaWrapper::registerVectorTypes() {
vec2f["computeSquaredLength"] = &Vec2f::computeSquaredLength<>;
vec2f["computeLength"] = &Vec2f::computeLength<>;
vec2f["normalize"] = &Vec2f::normalize<>;
vec2f["lerp"] = &Vec2f::lerp;
vec2f["nlerp"] = &Vec2f::nlerp;
vec2f["lerp"] = &Vec2f::lerp<>;
vec2f["nlerp"] = &Vec2f::nlerp<>;
vec2f["strictlyEquals"] = &Vec2f::strictlyEquals;
vec2f.set_function(sol::meta_function::unary_minus, PickOverload<>(&Vec2f::operator-));
vec2f.set_function(sol::meta_function::addition, sol::overload(PickOverload<const Vec2f&>(&Vec2f::operator+),
Expand Down Expand Up @@ -65,8 +65,8 @@ void LuaWrapper::registerVectorTypes() {
vec3f["computeSquaredLength"] = &Vec3f::computeSquaredLength<>;
vec3f["computeLength"] = &Vec3f::computeLength<>;
vec3f["normalize"] = &Vec3f::normalize<>;
vec3f["lerp"] = &Vec3f::lerp;
vec3f["nlerp"] = &Vec3f::nlerp;
vec3f["lerp"] = &Vec3f::lerp<>;
vec3f["nlerp"] = &Vec3f::nlerp<>;
vec3f["strictlyEquals"] = &Vec3f::strictlyEquals;
vec3f.set_function(sol::meta_function::unary_minus, PickOverload<>(&Vec3f::operator-));
vec3f.set_function(sol::meta_function::addition, sol::overload(PickOverload<const Vec3f&>(&Vec3f::operator+),
Expand Down Expand Up @@ -102,8 +102,8 @@ void LuaWrapper::registerVectorTypes() {
vec4f["computeSquaredLength"] = &Vec4f::computeSquaredLength<>;
vec4f["computeLength"] = &Vec4f::computeLength<>;
vec4f["normalize"] = &Vec4f::normalize<>;
vec4f["lerp"] = &Vec4f::lerp;
vec4f["nlerp"] = &Vec4f::nlerp;
vec4f["lerp"] = &Vec4f::lerp<>;
vec4f["nlerp"] = &Vec4f::nlerp<>;
vec4f["strictlyEquals"] = &Vec4f::strictlyEquals;
vec4f.set_function(sol::meta_function::unary_minus, PickOverload<>(&Vec4f::operator-));
vec4f.set_function(sol::meta_function::addition, sol::overload(PickOverload<const Vec4f&>(&Vec4f::operator+),
Expand Down
25 changes: 25 additions & 0 deletions tests/src/RaZ/Math/Vector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,31 @@ TEST_CASE("Vector normalization") {
}

TEST_CASE("Vector interpolation") {
CHECK(vec3b1.lerp(vec3b2, 0.f) == vec3b1);
CHECK(vec3b1.lerp(vec3b2, 0.25f) == Raz::Vec3b(35, 69, 9)); // Results are truncated, not rounded
CHECK(vec3b1.lerp(vec3b2, 0.5f) == Raz::Vec3b(39, 131, 6));
CHECK(vec3b1.lerp(vec3b2, 0.75f) == Raz::Vec3b(43, 193, 3));
CHECK(vec3b1.lerp(vec3b2, 1.f) == vec3b2);

// The resulting interpolated vector's type can be manually chosen
CHECK(vec3b1.lerp<float>(vec3b2, 0.f) == Raz::Vec3f(vec3b1));
CHECK(vec3b1.lerp<float>(vec3b2, 0.25f) == Raz::Vec3f(35.25f, 69.75f, 9.f));
CHECK(vec3b1.lerp<float>(vec3b2, 0.5f) == Raz::Vec3f(39.5f, 131.5f, 6.f));
CHECK(vec3b1.lerp<float>(vec3b2, 0.75f) == Raz::Vec3f(43.75f, 193.25f, 3.f));
CHECK(vec3b1.lerp<float>(vec3b2, 1.f) == Raz::Vec3f(vec3b2));

// Computing the lerp as-is then normalizing the result truncates the values, as lerp()'s returned vector is of the original type by default
CHECK_THAT(vec3b1.lerp(vec3b2, 0.5f).normalize(), IsNearlyEqualToVector(Raz::Vec3f(0.285059f, 0.9575061f, 0.0438552f)));
// Computing the normalized linear interpolation does the lerp with the same result type as it uses itself; no truncation happens
CHECK_THAT(vec3b1.nlerp(vec3b2, 0.5f), IsNearlyEqualToVector(Raz::Vec3f(0.2874076f, 0.956813f, 0.0436569f)));

CHECK(vec3i1.lerp(vec3i2, 0.f) == vec3i1);
CHECK(vec3i1.lerp(vec3i2, 0.25f) == Raz::Vec3i(149, -264, 14061));
CHECK(vec3i1.lerp(vec3i2, 0.5f) == Raz::Vec3i(346, -529, 9380));
CHECK(vec3i1.lerp(vec3i2, 0.75f) == Raz::Vec3i(543, -793, 4699));
CHECK(vec3i1.lerp(vec3i2, 1.f) == vec3i2);
CHECK(vec3i1.nlerp(vec3i2, 0.5f) == Raz::Vec3f(0.0368035f, -0.056269f, 0.997737f));

// lerp() doesn't normalize the resulting vector
CHECK(vec3f1.lerp(vec3f2, 0.f) == vec3f1);
CHECK(vec3f1.lerp(vec3f2, 0.25f) == Raz::Vec3f(137.73749f, 43.3125f, 2.23575f)); // (vec3f1 * 3 + vec3f2) / 4
Expand Down

0 comments on commit 80454bb

Please sign in to comment.