chore(math): tune cubic-bezier precison to 10bits
10 bits is already enough for normally used ease functions(ease, ease-in, ease-out, ease-in-out). 14 bits provides more accuracy and requires additional shift operation. >14bits provices more accuracy but requires 64bit calculation and additional shift operation. Add normally used ease function in test. Signed-off-by: Neo Xu <neo.xu1990@gmail.com>
This commit is contained in:
committed by
Gabor Kiss-Vamosi
parent
b374282e23
commit
043c48761f
@@ -16,8 +16,8 @@
|
||||
* TYPEDEFS
|
||||
**********************/
|
||||
|
||||
#define NEWTON_ITERATIONS 8
|
||||
#define CUBIC_PRECISION_BITS 20 /* 10 or 20 bits recommended, int64_t calculation is used for 20bit precision */
|
||||
#define CUBIC_NEWTON_ITERATIONS 8
|
||||
#define CUBIC_PRECISION_BITS 10 /* 10 or 14 bits recommended, int64_t calculation is used for >14bit precision */
|
||||
|
||||
#if CUBIC_PRECISION_BITS < 10 || CUBIC_PRECISION_BITS > 20
|
||||
#error "cubic precision bits should be in range of [10, 20] for 32bit/64bit calculations."
|
||||
@@ -103,12 +103,6 @@ uint32_t lv_bezier3(uint32_t t, uint32_t u0, uint32_t u1, uint32_t u2, uint32_t
|
||||
return v1 + v2 + v3 + v4;
|
||||
}
|
||||
|
||||
static float do_cubic_bezier_f(float t, float a, float b, float c)
|
||||
{
|
||||
/*a*t^3 + b*t^2 + c*t*/
|
||||
return ((a * t + b) * t + c) * t;
|
||||
}
|
||||
|
||||
/**
|
||||
* cubic-bezier Reference:
|
||||
*
|
||||
@@ -140,72 +134,10 @@ static float do_cubic_bezier_f(float t, float a, float b, float c)
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Calculate the y value of cubic-bezier(x1, y1, x2, y2) function as specified x.
|
||||
* @param x time in range of [0..1]
|
||||
* @param x1 x of control point 1 in range of [0..1]
|
||||
* @param y1 y of control point 1 in range of [0..1]
|
||||
* @param x2 x of control point 2 in range of [0..1]
|
||||
* @param y2 y of control point 2 in range of [0..1]
|
||||
* @return the value calculated
|
||||
*/
|
||||
float lv_cubic_bezier_f(float x, float x1, float y1, float x2, float y2)
|
||||
{
|
||||
float ax, bx, cx, ay, by, cy;
|
||||
float tl, tr, t; /*t in cubic-bezier function, used for bisection */
|
||||
float xs; /*x sampled on curve */
|
||||
float d; /*slope value at specified t*/
|
||||
|
||||
if(x == 0 || x == 1) return x;
|
||||
|
||||
cx = 3.f * x1;
|
||||
bx = 3.f * (x2 - x1) - cx;
|
||||
ax = 1.f - cx - bx;
|
||||
|
||||
cy = 3.f * y1;
|
||||
by = 3.f * (y2 - y1) - cy;
|
||||
ay = 1.f - cy - by;
|
||||
|
||||
/*Try Newton's method firstly */
|
||||
t = x; /*Make a guess*/
|
||||
for(int i = 0; i < NEWTON_ITERATIONS; i++) {
|
||||
xs = do_cubic_bezier_f(t, ax, bx, cx);
|
||||
xs -= x;
|
||||
if(LV_ABS(xs) < 1e-6f) goto found;
|
||||
|
||||
d = (3.f * ax * t + 2.f * bx) * t + cx;
|
||||
if(LV_ABS(d) < 1e-6f) break;
|
||||
t -= xs / d;
|
||||
}
|
||||
|
||||
/*Fallback to bisection method for reliability*/
|
||||
tl = 0.f, tr = 1.f, t = x;
|
||||
|
||||
if(t < tl) {
|
||||
t = tl;
|
||||
goto found;
|
||||
}
|
||||
|
||||
if(t > tr) {
|
||||
t = tr;
|
||||
goto found;
|
||||
}
|
||||
|
||||
while(tl < tr) {
|
||||
xs = do_cubic_bezier_f(t, ax, bx, cx);
|
||||
if(LV_ABS(xs - x) < 1e-6f) goto found;
|
||||
x > xs ? (tl = t) : (tr = t);
|
||||
t = (tr - tl) * .5f + tl;
|
||||
}
|
||||
|
||||
found:
|
||||
return do_cubic_bezier_f(t, ay, by, cy);
|
||||
}
|
||||
|
||||
static int32_t do_cubic_bezier(int32_t t, int32_t a, int32_t b, int32_t c)
|
||||
{
|
||||
/*a * t^3 + b * t^2 + c * t*/
|
||||
#if CUBIC_PRECISION_BITS > 10
|
||||
#if CUBIC_PRECISION_BITS > 14
|
||||
int64_t ret;
|
||||
#else
|
||||
int32_t ret;
|
||||
@@ -232,7 +164,7 @@ int32_t lv_cubic_bezier(int32_t x, int32_t x1, int32_t y1, int32_t x2, int32_t y
|
||||
int32_t ax, bx, cx, ay, by, cy;
|
||||
int32_t tl, tr, t; /*t in cubic-bezier function, used for bisection */
|
||||
int32_t xs; /*x sampled on curve */
|
||||
#if CUBIC_PRECISION_BITS > 10
|
||||
#if CUBIC_PRECISION_BITS > 14
|
||||
int64_t d; /*slope value at specified t*/
|
||||
#else
|
||||
int32_t d;
|
||||
@@ -260,7 +192,7 @@ int32_t lv_cubic_bezier(int32_t x, int32_t x1, int32_t y1, int32_t x2, int32_t y
|
||||
|
||||
/*Try Newton's method firstly */
|
||||
t = x; /*Make a guess*/
|
||||
for(int i = 0; i < NEWTON_ITERATIONS; i++) {
|
||||
for(int i = 0; i < CUBIC_NEWTON_ITERATIONS; i++) {
|
||||
/*Check if x on curve at t matches input x*/
|
||||
xs = do_cubic_bezier(t, ax, bx, cx) - x;
|
||||
if(LV_ABS(xs) <= 1) goto found;
|
||||
|
||||
@@ -75,17 +75,6 @@ uint32_t lv_bezier3(uint32_t t, uint32_t u0, uint32_t u1, uint32_t u2, uint32_t
|
||||
*/
|
||||
int32_t lv_cubic_bezier(int32_t x, int32_t x1, int32_t y1, int32_t x2, int32_t y2);
|
||||
|
||||
/**
|
||||
* Calculate the y value of cubic-bezier(x1, y1, x2, y2) function as specified x.
|
||||
* @param x time in range of [0..1]
|
||||
* @param x1 x of control point 1 in range of [0..1]
|
||||
* @param y1 y of control point 1 in range of [0..1]
|
||||
* @param x2 x of control point 2 in range of [0..1]
|
||||
* @param y2 y of control point 2 in range of [0..1]
|
||||
* @return the value calculated
|
||||
*/
|
||||
float lv_cubic_bezier_f(float x, float x1, float y1, float x2, float y2);
|
||||
|
||||
/**
|
||||
* Calculate the atan2 of a vector.
|
||||
* @param x
|
||||
|
||||
@@ -3,35 +3,113 @@
|
||||
|
||||
#include "unity/unity.h"
|
||||
|
||||
#define ERROR_THRESHOLD 5 /*5 in 1024, 0.5% max error allowed*/
|
||||
#define NEWTON_ITERATIONS 8
|
||||
|
||||
static float do_cubic_bezier_f(float t, float a, float b, float c)
|
||||
{
|
||||
/*a*t^3 + b*t^2 + c*t*/
|
||||
return ((a * t + b) * t + c) * t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the y value of cubic-bezier(x1, y1, x2, y2) function as specified x.
|
||||
* @param x time in range of [0..1]
|
||||
* @param x1 x of control point 1 in range of [0..1]
|
||||
* @param y1 y of control point 1 in range of [0..1]
|
||||
* @param x2 x of control point 2 in range of [0..1]
|
||||
* @param y2 y of control point 2 in range of [0..1]
|
||||
* @return the value calculated
|
||||
*/
|
||||
static float lv_cubic_bezier_f(float x, float x1, float y1, float x2, float y2)
|
||||
{
|
||||
float ax, bx, cx, ay, by, cy;
|
||||
float tl, tr, t; /*t in cubic-bezier function, used for bisection */
|
||||
float xs; /*x sampled on curve */
|
||||
float d; /*slope value at specified t*/
|
||||
|
||||
if(x == 0 || x == 1) return x;
|
||||
|
||||
cx = 3.f * x1;
|
||||
bx = 3.f * (x2 - x1) - cx;
|
||||
ax = 1.f - cx - bx;
|
||||
|
||||
cy = 3.f * y1;
|
||||
by = 3.f * (y2 - y1) - cy;
|
||||
ay = 1.f - cy - by;
|
||||
|
||||
/*Try Newton's method firstly */
|
||||
t = x; /*Make a guess*/
|
||||
for(int i = 0; i < NEWTON_ITERATIONS; i++) {
|
||||
xs = do_cubic_bezier_f(t, ax, bx, cx);
|
||||
xs -= x;
|
||||
if(LV_ABS(xs) < 1e-6f) goto found;
|
||||
|
||||
d = (3.f * ax * t + 2.f * bx) * t + cx;
|
||||
if(LV_ABS(d) < 1e-6f) break;
|
||||
t -= xs / d;
|
||||
}
|
||||
|
||||
/*Fallback to bisection method for reliability*/
|
||||
tl = 0.f, tr = 1.f, t = x;
|
||||
|
||||
if(t < tl) {
|
||||
t = tl;
|
||||
goto found;
|
||||
}
|
||||
|
||||
if(t > tr) {
|
||||
t = tr;
|
||||
goto found;
|
||||
}
|
||||
|
||||
while(tl < tr) {
|
||||
xs = do_cubic_bezier_f(t, ax, bx, cx);
|
||||
if(LV_ABS(xs - x) < 1e-6f) goto found;
|
||||
x > xs ? (tl = t) : (tr = t);
|
||||
t = (tr - tl) * .5f + tl;
|
||||
}
|
||||
|
||||
found:
|
||||
return do_cubic_bezier_f(t, ay, by, cy);
|
||||
}
|
||||
|
||||
|
||||
static int test_cubic_bezier_ease_functions(float fx1, float fy1, float fx2, float fy2)
|
||||
{
|
||||
int x1, y1, x2, y2, y;
|
||||
float t, t_step, fy;
|
||||
|
||||
t_step = .001f;
|
||||
x1 = fx1 * 1024;
|
||||
y1 = fy1 * 1024;
|
||||
x2 = fx2 * 1024;
|
||||
y2 = fy2 * 1024;
|
||||
|
||||
for(t = 0; t <= 1; t += t_step) {
|
||||
fy = lv_cubic_bezier_f(t, fx1, fy1, fx2, fy2);
|
||||
y = lv_cubic_bezier(t * 1024, x1, y1, x2, y2);
|
||||
if(LV_ABS(fy * 1024 - y) >= ERROR_THRESHOLD) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void test_math_cubic_bezier_result_should_be_precise(void)
|
||||
{
|
||||
int32_t x, x1, y1, x2, y2, y;
|
||||
int i = 0;
|
||||
while(i++ < 10000) {
|
||||
x1 = lv_rand(0, 1024);
|
||||
x2 = lv_rand(0, 1024);
|
||||
y1 = lv_rand(0, 1024);
|
||||
y2 = lv_rand(0, 1024);
|
||||
float fx1 = x1 / 1024.f;
|
||||
float fx2 = x2 / 1024.f;
|
||||
float fy1 = y1 / 1024.f;
|
||||
float fy2 = y2 / 1024.f;
|
||||
float fx, fy;
|
||||
/*ease-in-out function*/
|
||||
TEST_ASSERT_TRUE(test_cubic_bezier_ease_functions(.42, 0, .58, 1));
|
||||
|
||||
int j = 0;
|
||||
while(j++ < 100000) {
|
||||
x = lv_rand(0, 1024);
|
||||
y = lv_cubic_bezier(x, x1, y1, x2, y2);
|
||||
fx = x / 1024.f;
|
||||
fy = lv_cubic_bezier_f(fx, fx1, fy1, fx2, fy2);
|
||||
#if 1
|
||||
if (LV_ABS(fy * 1024 - y) > 250) {
|
||||
printf("x, x1, y1, x2, y2: %d,%d,%d,%d,%d\n", (int)x, (int)x1, (int)y1, (int)x2, (int)y2);
|
||||
}
|
||||
#endif
|
||||
TEST_ASSERT_LESS_OR_EQUAL(250, LV_ABS(fy * 1024 - y));
|
||||
}
|
||||
}
|
||||
/*ease-out function*/
|
||||
TEST_ASSERT_TRUE(test_cubic_bezier_ease_functions(0, 0, .58, 1));
|
||||
|
||||
/*ease-in function*/
|
||||
TEST_ASSERT_TRUE(test_cubic_bezier_ease_functions(.42, 0, 1, 1));
|
||||
|
||||
/*ease function*/
|
||||
TEST_ASSERT_TRUE(test_cubic_bezier_ease_functions(.25, .1, .25, 1));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user