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:
Neo Xu
2023-06-18 15:40:33 +08:00
committed by Gabor Kiss-Vamosi
parent b374282e23
commit 043c48761f
3 changed files with 109 additions and 110 deletions

View File

@@ -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;

View File

@@ -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

View File

@@ -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