Curves: Correct and improve Catmull Rom interpolation
Correct interpolation of integer POD types for Catmull Rom
interpolation as implemented in eaf416693d
.
**Problem description**
`attribute_math::DefaultMixer<T>::mix_in()` assumes/asserts positive
weights but the basis function for Catmull-Rom splines generates
negative weights (see image in revision). Passing negative weights will
yield correct result as sum(weights) = 1 (after multiplication by 0.5)
but the assert is still triggered in debug builds. This patch adjusts
the behavior by extending the mix functions with mix4(). The benefit
of using mix#() over a DefaultMixer is that the result no longer needs
to be divided by the weight sum, instead utilizing that the basis weight
sum is constant (see plot).
**Changes**
* Added mix4() and updated catmull_rom::interpolate() to use it.
* Removed TODOs from catmull_rom functions.
* Moved mix definitions to be ordered as 2, 3, 4 in the header.
**Implementation specifics**
`catmull_rom::interpolate()` uses a constexpr to differentiate between
POD types which multiplies the result with 0.5 after weighting the
values, this reduces the number of multiplications for 1D, 2D, 3D
vectors (https://godbolt.org/z/8M1z9Pxx6). While this could be
considered unnecessary, I didn't want to change the original behavior
as it could influence performance (did not measure performance here
as this should ensure the logic is ~identical for FP types).
Differential Revision: https://developer.blender.org/D15997
This commit is contained in:
parent
641bbc820f
commit
095516403c
|
@ -45,6 +45,58 @@ inline void convert_to_static_type(const eCustomDataType data_type, const Func &
|
|||
convert_to_static_type(cpp_type, func);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Mix two values of the same type.
|
||||
*
|
||||
* This is just basic linear interpolation.
|
||||
* \{ */
|
||||
|
||||
template<typename T> T mix2(float factor, const T &a, const T &b);
|
||||
|
||||
template<> inline bool mix2(const float factor, const bool &a, const bool &b)
|
||||
{
|
||||
return ((1.0f - factor) * a + factor * b) >= 0.5f;
|
||||
}
|
||||
|
||||
template<> inline int8_t mix2(const float factor, const int8_t &a, const int8_t &b)
|
||||
{
|
||||
return static_cast<int8_t>(std::round((1.0f - factor) * a + factor * b));
|
||||
}
|
||||
|
||||
template<> inline int mix2(const float factor, const int &a, const int &b)
|
||||
{
|
||||
return static_cast<int>(std::round((1.0f - factor) * a + factor * b));
|
||||
}
|
||||
|
||||
template<> inline float mix2(const float factor, const float &a, const float &b)
|
||||
{
|
||||
return (1.0f - factor) * a + factor * b;
|
||||
}
|
||||
|
||||
template<> inline float2 mix2(const float factor, const float2 &a, const float2 &b)
|
||||
{
|
||||
return math::interpolate(a, b, factor);
|
||||
}
|
||||
|
||||
template<> inline float3 mix2(const float factor, const float3 &a, const float3 &b)
|
||||
{
|
||||
return math::interpolate(a, b, factor);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline ColorGeometry4f mix2(const float factor, const ColorGeometry4f &a, const ColorGeometry4f &b)
|
||||
{
|
||||
return math::interpolate(a, b, factor);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline ColorGeometry4b mix2(const float factor, const ColorGeometry4b &a, const ColorGeometry4b &b)
|
||||
{
|
||||
return math::interpolate(a, b, factor);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Mix three values of the same type.
|
||||
*
|
||||
|
@ -117,53 +169,85 @@ inline ColorGeometry4b mix3(const float3 &weights,
|
|||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Mix two values of the same type.
|
||||
/** \name Mix four values of the same type.
|
||||
*
|
||||
* This is just basic linear interpolation.
|
||||
* \{ */
|
||||
|
||||
template<typename T> T mix2(float factor, const T &a, const T &b);
|
||||
template<typename T>
|
||||
T mix4(const float4 &weights, const T &v0, const T &v1, const T &v2, const T &v3);
|
||||
|
||||
template<> inline bool mix2(const float factor, const bool &a, const bool &b)
|
||||
template<>
|
||||
inline int8_t mix4(
|
||||
const float4 &weights, const int8_t &v0, const int8_t &v1, const int8_t &v2, const int8_t &v3)
|
||||
{
|
||||
return ((1.0f - factor) * a + factor * b) >= 0.5f;
|
||||
}
|
||||
|
||||
template<> inline int8_t mix2(const float factor, const int8_t &a, const int8_t &b)
|
||||
{
|
||||
return static_cast<int8_t>(std::round((1.0f - factor) * a + factor * b));
|
||||
}
|
||||
|
||||
template<> inline int mix2(const float factor, const int &a, const int &b)
|
||||
{
|
||||
return static_cast<int>(std::round((1.0f - factor) * a + factor * b));
|
||||
}
|
||||
|
||||
template<> inline float mix2(const float factor, const float &a, const float &b)
|
||||
{
|
||||
return (1.0f - factor) * a + factor * b;
|
||||
}
|
||||
|
||||
template<> inline float2 mix2(const float factor, const float2 &a, const float2 &b)
|
||||
{
|
||||
return math::interpolate(a, b, factor);
|
||||
}
|
||||
|
||||
template<> inline float3 mix2(const float factor, const float3 &a, const float3 &b)
|
||||
{
|
||||
return math::interpolate(a, b, factor);
|
||||
return static_cast<int8_t>(
|
||||
std::round(weights.x * v0 + weights.y * v1 + weights.z * v2 + weights.w * v3));
|
||||
}
|
||||
|
||||
template<>
|
||||
inline ColorGeometry4f mix2(const float factor, const ColorGeometry4f &a, const ColorGeometry4f &b)
|
||||
inline bool mix4(
|
||||
const float4 &weights, const bool &v0, const bool &v1, const bool &v2, const bool &v3)
|
||||
{
|
||||
return math::interpolate(a, b, factor);
|
||||
return (weights.x * v0 + weights.y * v1 + weights.z * v2 + weights.w * v3) >= 0.5f;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline ColorGeometry4b mix2(const float factor, const ColorGeometry4b &a, const ColorGeometry4b &b)
|
||||
inline int mix4(const float4 &weights, const int &v0, const int &v1, const int &v2, const int &v3)
|
||||
{
|
||||
return math::interpolate(a, b, factor);
|
||||
return static_cast<int>(
|
||||
std::round(weights.x * v0 + weights.y * v1 + weights.z * v2 + weights.w * v3));
|
||||
}
|
||||
|
||||
template<>
|
||||
inline float mix4(
|
||||
const float4 &weights, const float &v0, const float &v1, const float &v2, const float &v3)
|
||||
{
|
||||
return weights.x * v0 + weights.y * v1 + weights.z * v2 + weights.w * v3;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline float2 mix4(
|
||||
const float4 &weights, const float2 &v0, const float2 &v1, const float2 &v2, const float2 &v3)
|
||||
{
|
||||
return weights.x * v0 + weights.y * v1 + weights.z * v2 + weights.w * v3;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline float3 mix4(
|
||||
const float4 &weights, const float3 &v0, const float3 &v1, const float3 &v2, const float3 &v3)
|
||||
{
|
||||
return weights.x * v0 + weights.y * v1 + weights.z * v2 + weights.w * v3;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline ColorGeometry4f mix4(const float4 &weights,
|
||||
const ColorGeometry4f &v0,
|
||||
const ColorGeometry4f &v1,
|
||||
const ColorGeometry4f &v2,
|
||||
const ColorGeometry4f &v3)
|
||||
{
|
||||
ColorGeometry4f result;
|
||||
interp_v4_v4v4v4v4(result, v0, v1, v2, v3, weights);
|
||||
return result;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline ColorGeometry4b mix4(const float4 &weights,
|
||||
const ColorGeometry4b &v0,
|
||||
const ColorGeometry4b &v1,
|
||||
const ColorGeometry4b &v2,
|
||||
const ColorGeometry4b &v3)
|
||||
{
|
||||
const float4 v0_f{&v0.r};
|
||||
const float4 v1_f{&v1.r};
|
||||
const float4 v2_f{&v2.r};
|
||||
const float4 v3_f{&v3.r};
|
||||
float4 mixed;
|
||||
interp_v4_v4v4v4v4(mixed, v0_f, v1_f, v2_f, v3_f, weights);
|
||||
return ColorGeometry4b{static_cast<uint8_t>(mixed[0]),
|
||||
static_cast<uint8_t>(mixed[1]),
|
||||
static_cast<uint8_t>(mixed[2]),
|
||||
static_cast<uint8_t>(mixed[3])};
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -709,7 +709,7 @@ void interpolate_to_evaluated(const GSpan src,
|
|||
const Span<int> evaluated_offsets,
|
||||
GMutableSpan dst);
|
||||
|
||||
void calculate_basis(const float parameter, float r_weights[4]);
|
||||
void calculate_basis(const float parameter, float4 &r_weights);
|
||||
|
||||
/**
|
||||
* Interpolate the control point values for the given parameter on the piecewise segment.
|
||||
|
@ -720,22 +720,15 @@ void calculate_basis(const float parameter, float r_weights[4]);
|
|||
template<typename T>
|
||||
T interpolate(const T &a, const T &b, const T &c, const T &d, const float parameter)
|
||||
{
|
||||
float n[4];
|
||||
BLI_assert(0.0f <= parameter && parameter <= 1.0f);
|
||||
float4 n;
|
||||
calculate_basis(parameter, n);
|
||||
/* TODO: Use DefaultMixer or other generic mixing in the basis evaluation function to simplify
|
||||
* supporting more types. */
|
||||
if constexpr (!is_same_any_v<T, float, float2, float3, float4, int8_t, int, int64_t>) {
|
||||
T return_value;
|
||||
attribute_math::DefaultMixer<T> mixer({&return_value, 1});
|
||||
mixer.mix_in(0, a, n[0] * 0.5f);
|
||||
mixer.mix_in(0, b, n[1] * 0.5f);
|
||||
mixer.mix_in(0, c, n[2] * 0.5f);
|
||||
mixer.mix_in(0, d, n[3] * 0.5f);
|
||||
mixer.finalize();
|
||||
return return_value;
|
||||
if constexpr (is_same_any_v<T, float, float2, float3>) {
|
||||
/* Save multiplications by adjusting weights after mix. */
|
||||
return 0.5f * attribute_math::mix4<T>(n, a, b, c, d);
|
||||
}
|
||||
else {
|
||||
return 0.5f * (a * n[0] + b * n[1] + c * n[2] + d * n[3]);
|
||||
return attribute_math::mix4<T>(n * 0.5f, a, b, c, d);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ int calculate_evaluated_num(const int points_num, const bool cyclic, const int r
|
|||
}
|
||||
|
||||
/* Adapted from Cycles #catmull_rom_basis_eval function. */
|
||||
void calculate_basis(const float parameter, float r_weights[4])
|
||||
void calculate_basis(const float parameter, float4 &r_weights)
|
||||
{
|
||||
const float t = parameter;
|
||||
const float s = 1.0f - parameter;
|
||||
|
@ -139,11 +139,7 @@ void interpolate_to_evaluated(const GSpan src,
|
|||
{
|
||||
attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
/* TODO: Use DefaultMixer or other generic mixing in the basis evaluation function to simplify
|
||||
* supporting more types. */
|
||||
if constexpr (is_same_any_v<T, float, float2, float3, float4, int8_t, int, int64_t>) {
|
||||
interpolate_to_evaluated(src.typed<T>(), cyclic, resolution, dst.typed<T>());
|
||||
}
|
||||
interpolate_to_evaluated(src.typed<T>(), cyclic, resolution, dst.typed<T>());
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -154,11 +150,7 @@ void interpolate_to_evaluated(const GSpan src,
|
|||
{
|
||||
attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
/* TODO: Use DefaultMixer or other generic mixing in the basis evaluation function to simplify
|
||||
* supporting more types. */
|
||||
if constexpr (is_same_any_v<T, float, float2, float3, float4, int8_t, int, int64_t>) {
|
||||
interpolate_to_evaluated(src.typed<T>(), cyclic, evaluated_offsets, dst.typed<T>());
|
||||
}
|
||||
interpolate_to_evaluated(src.typed<T>(), cyclic, evaluated_offsets, dst.typed<T>());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue