From c56ac246024162970e3cd63db2dc7f02eb0bb57b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 30 Sep 2015 11:36:28 -0700 Subject: [PATCH 1/2] Improve particle property interpolation Use Bezier interpolation instead of cubic so that values don't overshoot. --- .../entities/src/ParticleEffectEntityItem.cpp | 21 +++---- libraries/shared/src/Interpolate.cpp | 59 ++++++++++--------- libraries/shared/src/Interpolate.h | 10 ++-- 3 files changed, 47 insertions(+), 43 deletions(-) diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 9d11dff55a..40d3853c39 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -594,25 +594,22 @@ QString ParticleEffectEntityItem::getAnimationSettings() const { } void ParticleEffectEntityItem::updateRadius(quint32 index, float age) { - _particleRadiuses[index] = Interpolate::cubicInterpolate3Points(_radiusStarts[index], _radiusMiddles[index], + _particleRadiuses[index] = Interpolate::interpolate3Points(_radiusStarts[index], _radiusMiddles[index], _radiusFinishes[index], age); } void ParticleEffectEntityItem::updateColor(quint32 index, float age) { - _particleColors[index].red = - (int)glm::clamp(Interpolate::cubicInterpolate3Points(_colorStarts[index].red, _colorMiddles[index].red, - _colorFinishes[index].red, age), 0.0f, 255.0f); - _particleColors[index].green = - (int)glm::clamp(Interpolate::cubicInterpolate3Points(_colorStarts[index].green, _colorMiddles[index].green, - _colorFinishes[index].green, age), 0.0f, 255.0f); - _particleColors[index].blue = - (int)glm::clamp(Interpolate::cubicInterpolate3Points(_colorStarts[index].blue, _colorMiddles[index].blue, - _colorFinishes[index].blue, age), 0.0f, 255.0f); + _particleColors[index].red = (int)Interpolate::interpolate3Points(_colorStarts[index].red, _colorMiddles[index].red, + _colorFinishes[index].red, age); + _particleColors[index].green = (int)Interpolate::interpolate3Points(_colorStarts[index].green, _colorMiddles[index].green, + _colorFinishes[index].green, age); + _particleColors[index].blue = (int)Interpolate::interpolate3Points(_colorStarts[index].blue, _colorMiddles[index].blue, + _colorFinishes[index].blue, age); } void ParticleEffectEntityItem::updateAlpha(quint32 index, float age) { - _particleAlphas[index] = glm::clamp(Interpolate::cubicInterpolate3Points(_alphaStarts[index], _alphaMiddles[index], - _alphaFinishes[index], age), 0.0f, 1.0f); + _particleAlphas[index] = Interpolate::interpolate3Points(_alphaStarts[index], _alphaMiddles[index], + _alphaFinishes[index], age); } void ParticleEffectEntityItem::extendBounds(const glm::vec3& point) { diff --git a/libraries/shared/src/Interpolate.cpp b/libraries/shared/src/Interpolate.cpp index 7c18236bd0..bc18c087ad 100644 --- a/libraries/shared/src/Interpolate.cpp +++ b/libraries/shared/src/Interpolate.cpp @@ -11,41 +11,46 @@ #include "Interpolate.h" -float Interpolate::cubicInterpolate2Points(float y0, float y1, float y2, float y3, float u) { - float a0, a1, a2, a3, uu, uuu; +#include - a0 = y3 - y2 - y0 + y1; - a1 = y0 - y1 - a0; - a2 = y2 - y0; - a3 = y1; - - uu = u * u; - uuu = uu * u; - - return (a0 * uuu + a1 * uu + a2 * u + a3); +float Interpolate::bezierInterpolate(float y1, float y2, float y3, float u) { + // https://en.wikipedia.org/wiki/Bezier_curve + return (1.0f - u) * (1.0f - u) * y1 + 2.0f * (1.0f - u) * u * y2 + u * u * y3; } -float Interpolate::cubicInterpolate3Points(float y1, float y2, float y3, float u) { - float y0, y4; +float Interpolate::interpolate3Points(float y1, float y2, float y3, float u) { + if (u <= 0.5f && y1 == y2 || u >= 0.5f && y2 == y3) { + // Flat line. + return y2; + } - if (u <= 0.5f) { - if (y1 == y2) { - return y2; + if (y2 >= y1 && y2 >= y3 || y2 <= y1 && y2 <= y3) { + // U or inverted-U shape. + // Make the slope at y2 = 0, which means that the control points half way between the value points have the value y2. + if (u <= 0.5f) { + return bezierInterpolate(y1, y2, y2, 2.0f * u); + } else { + return bezierInterpolate(y2, y2, y3, 2.0f * u - 1.0f); } - y0 = 2.0f * y1 - y2; // y0 is linear extension of line from y2 to y1. - u = 2.0f * u; - - return Interpolate::cubicInterpolate2Points(y0, y1, y2, y3, u); - } else { - if (y2 == y3) { - return y2; + // L or inverted and/or mirrored L shape. + // Make the slope at y2 be the slope between y1 and y3, up to a maximum of double the minimum of the slopes between y1 + // and y2, and y2 and y3. Use this slope to calculate the control points half way between the value points. + // Note: The maximum ensures that the control points and therefore the interpolated values stay between y1 and y3. + float slope = y3 - y1; + float slope12 = y2 - y1; + float slope23 = y3 - y2; + if (fabsf(slope) > fabsf(2.0f * slope12)) { + slope = 2.0f * slope12; + } else if (fabsf(slope) > fabsf(2.0f * slope23)) { + slope = 2.0f * slope23; } - y4 = 2.0f * y3 - y2; // y4 is linear extension of line from y2 to y3. - u = 2.0f * u - 1.0f; - - return Interpolate::cubicInterpolate2Points(y1, y2, y3, y4, u); + if (u <= 0.5f) { + return bezierInterpolate(y1, y2 - slope / 2.0f, y2, 2.0f * u); + } else { + return bezierInterpolate(y2, y2 + slope / 2.0f, y3, 2.0f * u - 1.0f); + } } } diff --git a/libraries/shared/src/Interpolate.h b/libraries/shared/src/Interpolate.h index 919d075b15..316bee1339 100644 --- a/libraries/shared/src/Interpolate.h +++ b/libraries/shared/src/Interpolate.h @@ -15,11 +15,13 @@ class Interpolate { public: - // Cubic interpolation at position u [0.0 - 1.0] between values y1 and y2 with equidistant values y0 and y3 either side. - static float cubicInterpolate2Points(float y0, float y1, float y2, float y3, float u); + // Bezier interpolate at position u [0.0 - 1.0] between y values equally spaced along the x-axis. The interpolated values + // pass through y1 and y3 but not y2; y2 is the Bezier control point. + static float bezierInterpolate(float y1, float y2, float y3, float u); - // Cubic interpolation at position u [0.0 - 1.0] between values y1 and y3 with midpoint value y2. - static float cubicInterpolate3Points(float y1, float y2, float y3, float u); + // Interpolate at position u [0.0 - 1.0] between y values equally spaced along the x-axis such that the interpolated values + // pass through all three y values. Return value lies wholly within the range of y values passed in. + static float interpolate3Points(float y1, float y2, float y3, float u); }; #endif // hifi_Interpolate_h From e56d2d9b434622a63e1485ea5e16b945202d2ba6 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 1 Oct 2015 15:48:37 -0700 Subject: [PATCH 2/2] Add asserts --- libraries/shared/src/Interpolate.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/shared/src/Interpolate.cpp b/libraries/shared/src/Interpolate.cpp index bc18c087ad..7acc0c8cbd 100644 --- a/libraries/shared/src/Interpolate.cpp +++ b/libraries/shared/src/Interpolate.cpp @@ -11,14 +11,18 @@ #include "Interpolate.h" +#include #include float Interpolate::bezierInterpolate(float y1, float y2, float y3, float u) { // https://en.wikipedia.org/wiki/Bezier_curve + assert(0.0f <= u && u <= 1.0f); return (1.0f - u) * (1.0f - u) * y1 + 2.0f * (1.0f - u) * u * y2 + u * u * y3; } float Interpolate::interpolate3Points(float y1, float y2, float y3, float u) { + assert(0.0f <= u && u <= 1.0f); + if (u <= 0.5f && y1 == y2 || u >= 0.5f && y2 == y3) { // Flat line. return y2;