diff options
Diffstat (limited to 'extern/curve_fit_nd/intern/curve_fit_cubic.c')
-rw-r--r-- | extern/curve_fit_nd/intern/curve_fit_cubic.c | 1034 |
1 files changed, 1034 insertions, 0 deletions
diff --git a/extern/curve_fit_nd/intern/curve_fit_cubic.c b/extern/curve_fit_nd/intern/curve_fit_cubic.c new file mode 100644 index 00000000000..810cf92760d --- /dev/null +++ b/extern/curve_fit_nd/intern/curve_fit_cubic.c @@ -0,0 +1,1034 @@ +/* + * Copyright (c) 2016, DWANGO Co., Ltd. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the <organization> nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file curve_fit_cubic.c + * \ingroup curve_fit + */ + +#include <math.h> +#include <float.h> +#include <stdbool.h> +#include <assert.h> + +#include <string.h> +#include <stdlib.h> + +#include "../curve_fit_nd.h" + +/* avoid re-calculating lengths multiple times */ +#define USE_LENGTH_CACHE + +/* store the indices in the cubic data so we can return the original indices, + * useful when the caller has data assosiated with the curve. */ +#define USE_ORIG_INDEX_DATA + +typedef unsigned int uint; + +#include "curve_fit_inline.h" + +#ifdef _MSC_VER +# define alloca(size) _alloca(size) +#endif + +#if !defined(_MSC_VER) +# define USE_VLA +#endif + +#ifdef USE_VLA +# ifdef __GNUC__ +# pragma GCC diagnostic ignored "-Wvla" +# endif +#else +# ifdef __GNUC__ +# pragma GCC diagnostic error "-Wvla" +# endif +#endif + +#define SWAP(type, a, b) { \ + type sw_ap; \ + sw_ap = (a); \ + (a) = (b); \ + (b) = sw_ap; \ +} (void)0 + + +/* -------------------------------------------------------------------- */ + +/** \name Cubic Type & Functions + * \{ */ + +typedef struct Cubic { + /* single linked lists */ + struct Cubic *next; +#ifdef USE_ORIG_INDEX_DATA + uint orig_span; +#endif + /* 0: point_0, 1: handle_0, 2: handle_1, 3: point_1, + * each one is offset by 'dims' */ + double pt_data[0]; +} Cubic; + +#define CUBIC_PT(cubic, index, dims) \ + (&(cubic)->pt_data[(index) * (dims)]) + +#define CUBIC_VARS(c, dims, _p0, _p1, _p2, _p3) \ + double \ + *_p0 = (c)->pt_data, \ + *_p1 = _p0 + (dims), \ + *_p2 = _p1 + (dims), \ + *_p3 = _p2 + (dims); ((void)0) +#define CUBIC_VARS_CONST(c, dims, _p0, _p1, _p2, _p3) \ + const double \ + *_p0 = (c)->pt_data, \ + *_p1 = _p0 + (dims), \ + *_p2 = _p1 + (dims), \ + *_p3 = _p2 + (dims); ((void)0) + + +static Cubic *cubic_alloc(const uint dims) +{ + return malloc(sizeof(Cubic) + (sizeof(double) * 4 * dims)); +} + +static void cubic_init( + Cubic *cubic, + const double p0[], const double p1[], const double p2[], const double p3[], + const uint dims) +{ + copy_vnvn(CUBIC_PT(cubic, 0, dims), p0, dims); + copy_vnvn(CUBIC_PT(cubic, 1, dims), p1, dims); + copy_vnvn(CUBIC_PT(cubic, 2, dims), p2, dims); + copy_vnvn(CUBIC_PT(cubic, 3, dims), p3, dims); +} + +static void cubic_free(Cubic *cubic) +{ + free(cubic); +} + +/** \} */ + + +/* -------------------------------------------------------------------- */ + +/** \name CubicList Type & Functions + * \{ */ + +typedef struct CubicList { + struct Cubic *items; + uint len; + uint dims; +} CubicList; + +static void cubic_list_prepend(CubicList *clist, Cubic *cubic) +{ + cubic->next = clist->items; + clist->items = cubic; + clist->len++; +} + +static double *cubic_list_as_array( + const CubicList *clist +#ifdef USE_ORIG_INDEX_DATA + , + const uint index_last, + uint *r_orig_index +#endif + ) +{ + const uint dims = clist->dims; + const uint array_flat_len = (clist->len + 1) * 3 * dims; + + double *array = malloc(sizeof(double) * array_flat_len); + const double *handle_prev = &((Cubic *)clist->items)->pt_data[dims]; + +#ifdef USE_ORIG_INDEX_DATA + uint orig_index_value = index_last; + uint orig_index_index = clist->len; + bool use_orig_index = (r_orig_index != NULL); +#endif + + /* fill the array backwards */ + const size_t array_chunk = 3 * dims; + double *array_iter = array + array_flat_len; + for (Cubic *citer = clist->items; citer; citer = citer->next) { + array_iter -= array_chunk; + memcpy(array_iter, &citer->pt_data[2 * dims], sizeof(double) * 2 * dims); + memcpy(&array_iter[2 * dims], &handle_prev[dims], sizeof(double) * dims); + handle_prev = citer->pt_data; + +#ifdef USE_ORIG_INDEX_DATA + if (use_orig_index) { + r_orig_index[orig_index_index--] = orig_index_value; + orig_index_value -= citer->orig_span; + } +#endif + } + +#ifdef USE_ORIG_INDEX_DATA + if (use_orig_index) { + assert(orig_index_index == 0); + assert(orig_index_value == 0 || index_last == 0); + r_orig_index[orig_index_index] = index_last ? orig_index_value : 0; + + } +#endif + + /* flip tangent for first and last (we could leave at zero, but set to something useful) */ + + /* first */ + array_iter -= array_chunk; + memcpy(&array_iter[dims], handle_prev, sizeof(double) * 2 * dims); + flip_vn_vnvn(&array_iter[0 * dims], &array_iter[1 * dims], &array_iter[2 * dims], dims); + assert(array == array_iter); + + /* last */ + array_iter += array_flat_len - (3 * dims); + flip_vn_vnvn(&array_iter[2 * dims], &array_iter[1 * dims], &array_iter[0 * dims], dims); + + return array; +} + +static void cubic_list_clear(CubicList *clist) +{ + Cubic *cubic_next; + for (Cubic *citer = clist->items; citer; citer = cubic_next) { + cubic_next = citer->next; + cubic_free(citer); + } + clist->items = NULL; + clist->len = 0; +} + +/** \} */ + + +/* -------------------------------------------------------------------- */ + +/** \name Cubic Evaluation + * \{ */ + +static void cubic_evaluate( + const Cubic *cubic, const double t, const uint dims, + double r_v[]) +{ + CUBIC_VARS_CONST(cubic, dims, p0, p1, p2, p3); + const double s = 1.0 - t; + + for (uint j = 0; j < dims; j++) { + const double p01 = (p0[j] * s) + (p1[j] * t); + const double p12 = (p1[j] * s) + (p2[j] * t); + const double p23 = (p2[j] * s) + (p3[j] * t); + r_v[j] = ((((p01 * s) + (p12 * t))) * s) + + ((((p12 * s) + (p23 * t))) * t); + } +} + +static void cubic_calc_point( + const Cubic *cubic, const double t, const uint dims, + double r_v[]) +{ + CUBIC_VARS_CONST(cubic, dims, p0, p1, p2, p3); + const double s = 1.0 - t; + for (uint j = 0; j < dims; j++) { + r_v[j] = p0[j] * s * s * s + + 3.0 * t * s * (s * p1[j] + t * p2[j]) + t * t * t * p3[j]; + } +} + +static void cubic_calc_speed( + const Cubic *cubic, const double t, const uint dims, + double r_v[]) +{ + CUBIC_VARS_CONST(cubic, dims, p0, p1, p2, p3); + const double s = 1.0 - t; + for (uint j = 0; j < dims; j++) { + r_v[j] = 3.0 * ((p1[j] - p0[j]) * s * s + 2.0 * + (p2[j] - p0[j]) * s * t + + (p3[j] - p2[j]) * t * t); + } +} + +static void cubic_calc_acceleration( + const Cubic *cubic, const double t, const uint dims, + double r_v[]) +{ + CUBIC_VARS_CONST(cubic, dims, p0, p1, p2, p3); + const double s = 1.0 - t; + for (uint j = 0; j < dims; j++) { + r_v[j] = 6.0 * ((p2[j] - 2.0 * p1[j] + p0[j]) * s + + (p3[j] - 2.0 * p2[j] + p1[j]) * t); + } +} + +/** + * Returns a 'measure' of the maximal discrepancy of the points specified + * by points_offset from the corresponding cubic(u[]) points. + */ +static void cubic_calc_error( + const Cubic *cubic, + const double *points_offset, + const uint points_offset_len, + const double *u, + const uint dims, + + double *r_error_sq_max, + uint *r_error_index) +{ + double error_sq_max = 0.0; + uint error_index = 0; + + const double *pt_real = points_offset + dims; +#ifdef USE_VLA + double pt_eval[dims]; +#else + double *pt_eval = alloca(sizeof(double) * dims); +#endif + + for (uint i = 1; i < points_offset_len - 1; i++, pt_real += dims) { + cubic_evaluate(cubic, u[i], dims, pt_eval); + + const double err_sq = len_squared_vnvn(pt_real, pt_eval, dims); + if (err_sq >= error_sq_max) { + error_sq_max = err_sq; + error_index = i; + } + } + + *r_error_sq_max = error_sq_max; + *r_error_index = error_index; +} + +/** + * Bezier multipliers + */ + +static double B1(double u) +{ + double tmp = 1.0 - u; + return 3.0 * u * tmp * tmp; +} + +static double B2(double u) +{ + return 3.0 * u * u * (1.0 - u); +} + +static double B0plusB1(double u) +{ + double tmp = 1.0 - u; + return tmp * tmp * (1.0 + 2.0 * u); +} + +static double B2plusB3(double u) +{ + return u * u * (3.0 - 2.0 * u); +} + +static void points_calc_center_weighted( + const double *points_offset, + const uint points_offset_len, + const uint dims, + + double r_center[]) +{ + /* + * Calculate a center that compensates for point spacing. + */ + + const double *pt_prev = &points_offset[(points_offset_len - 2) * dims]; + const double *pt_curr = pt_prev + dims; + const double *pt_next = points_offset; + + double w_prev = len_vnvn(pt_prev, pt_curr, dims); + + zero_vn(r_center, dims); + double w_tot = 0.0; + + for (uint i_next = 0; i_next < points_offset_len; i_next++) { + const double w_next = len_vnvn(pt_curr, pt_next, dims); + const double w = w_prev + w_next; + w_tot += w; + + miadd_vn_vn_fl(r_center, pt_curr, w, dims); + + w_prev = w_next; + + pt_prev = pt_curr; + pt_curr = pt_next; + pt_next += dims; + } + + if (w_tot != 0.0) { + imul_vn_fl(r_center, 1.0 / w_tot, dims); + } +} + +/** + * Use least-squares method to find Bezier control points for region. + */ +static void cubic_from_points( + const double *points_offset, + const uint points_offset_len, + const double *u_prime, + const double tan_l[], + const double tan_r[], + const uint dims, + + Cubic *r_cubic) +{ + + const double *p0 = &points_offset[0]; + const double *p3 = &points_offset[(points_offset_len - 1) * dims]; + + /* Point Pairs */ + double alpha_l, alpha_r; +#ifdef USE_VLA + double a[2][dims]; + double tmp[dims]; +#else + double *a[2] = { + alloca(sizeof(double) * dims), + alloca(sizeof(double) * dims), + }; + double *tmp = alloca(sizeof(double) * dims); +#endif + + { + double x[2] = {0.0}, c[2][2] = {{0.0}}; + const double *pt = points_offset; + + for (uint i = 0; i < points_offset_len; i++, pt += dims) { + mul_vnvn_fl(a[0], tan_l, B1(u_prime[i]), dims); + mul_vnvn_fl(a[1], tan_r, B2(u_prime[i]), dims); + + c[0][0] += dot_vnvn(a[0], a[0], dims); + c[0][1] += dot_vnvn(a[0], a[1], dims); + c[1][1] += dot_vnvn(a[1], a[1], dims); + + c[1][0] = c[0][1]; + + { + const double b0_plus_b1 = B0plusB1(u_prime[i]); + const double b2_plus_b3 = B2plusB3(u_prime[i]); + for (uint j = 0; j < dims; j++) { + tmp[j] = (pt[j] - (p0[j] * b0_plus_b1)) + (p3[j] * b2_plus_b3); + } + + x[0] += dot_vnvn(a[0], tmp, dims); + x[1] += dot_vnvn(a[1], tmp, dims); + } + } + + double det_C0_C1 = c[0][0] * c[1][1] - c[0][1] * c[1][0]; + double det_C_0X = x[1] * c[0][0] - x[0] * c[0][1]; + double det_X_C1 = x[0] * c[1][1] - x[1] * c[0][1]; + + if (is_almost_zero(det_C0_C1)) { + det_C0_C1 = c[0][0] * c[1][1] * 10e-12; + } + + /* may still divide-by-zero, check below will catch nan values */ + alpha_l = det_X_C1 / det_C0_C1; + alpha_r = det_C_0X / det_C0_C1; + } + + /* + * The problem that the stupid values for alpha dare not put + * only when we realize that the sign and wrong, + * but even if the values are too high. + * But how do you evaluate it? + * + * Meanwhile, we should ensure that these values are sometimes + * so only problems absurd of approximation and not for bugs in the code. + */ + + /* flip check to catch nan values */ + if (!(alpha_l >= 0.0) || + !(alpha_r >= 0.0)) + { + alpha_l = alpha_r = len_vnvn(p0, p3, dims) / 3.0; + } + + double *p1 = CUBIC_PT(r_cubic, 1, dims); + double *p2 = CUBIC_PT(r_cubic, 2, dims); + + copy_vnvn(CUBIC_PT(r_cubic, 0, dims), p0, dims); + copy_vnvn(CUBIC_PT(r_cubic, 3, dims), p3, dims); + +#ifdef USE_ORIG_INDEX_DATA + r_cubic->orig_span = (points_offset_len - 1); +#endif + + /* p1 = p0 - (tan_l * alpha_l); + * p2 = p3 + (tan_r * alpha_r); + */ + msub_vn_vnvn_fl(p1, p0, tan_l, alpha_l, dims); + madd_vn_vnvn_fl(p2, p3, tan_r, alpha_r, dims); + + /* ------------------------------------ + * Clamping (we could make it optional) + */ +#ifdef USE_VLA + double center[dims]; +#else + double *center = alloca(sizeof(double) * dims); +#endif + points_calc_center_weighted(points_offset, points_offset_len, dims, center); + + const double clamp_scale = 3.0; /* clamp to 3x */ + double dist_sq_max = 0.0; + + { + const double *pt = points_offset; + for (uint i = 0; i < points_offset_len; i++, pt += dims) { +#if 0 + double dist_sq_test = sq(len_vnvn(center, pt, dims) * clamp_scale); +#else + /* do inline */ + double dist_sq_test = 0.0; + for (uint j = 0; j < dims; j++) { + dist_sq_test += sq((pt[j] - center[j]) * clamp_scale); + } +#endif + dist_sq_max = max(dist_sq_max, dist_sq_test); + } + } + + double p1_dist_sq = len_squared_vnvn(center, p1, dims); + double p2_dist_sq = len_squared_vnvn(center, p2, dims); + + if (p1_dist_sq > dist_sq_max || + p2_dist_sq > dist_sq_max) + { + + alpha_l = alpha_r = len_vnvn(p0, p3, dims) / 3.0; + + /* + * p1 = p0 - (tan_l * alpha_l); + * p2 = p3 + (tan_r * alpha_r); + */ + for (uint j = 0; j < dims; j++) { + p1[j] = p0[j] - (tan_l[j] * alpha_l); + p2[j] = p3[j] + (tan_r[j] * alpha_r); + } + + p1_dist_sq = len_squared_vnvn(center, p1, dims); + p2_dist_sq = len_squared_vnvn(center, p2, dims); + } + + /* clamp within the 3x radius */ + if (p1_dist_sq > dist_sq_max) { + isub_vnvn(p1, center, dims); + imul_vn_fl(p1, sqrt(dist_sq_max) / sqrt(p1_dist_sq), dims); + iadd_vnvn(p1, center, dims); + } + if (p2_dist_sq > dist_sq_max) { + isub_vnvn(p2, center, dims); + imul_vn_fl(p2, sqrt(dist_sq_max) / sqrt(p2_dist_sq), dims); + iadd_vnvn(p2, center, dims); + } + /* end clamping */ +} + +#ifdef USE_LENGTH_CACHE +static void points_calc_coord_length_cache( + const double *points_offset, + const uint points_offset_len, + const uint dims, + + double *r_points_length_cache) +{ + const double *pt_prev = points_offset; + const double *pt = pt_prev + dims; + r_points_length_cache[0] = 0.0; + for (uint i = 1; i < points_offset_len; i++) { + r_points_length_cache[i] = len_vnvn(pt, pt_prev, dims); + pt_prev = pt; + pt += dims; + } +} +#endif /* USE_LENGTH_CACHE */ + + +static void points_calc_coord_length( + const double *points_offset, + const uint points_offset_len, + const uint dims, +#ifdef USE_LENGTH_CACHE + const double *points_length_cache, +#endif + double *r_u) +{ + const double *pt_prev = points_offset; + const double *pt = pt_prev + dims; + r_u[0] = 0.0; + for (uint i = 1, i_prev = 0; i < points_offset_len; i++) { + double length; + +#ifdef USE_LENGTH_CACHE + length = points_length_cache[i]; + + assert(len_vnvn(pt, pt_prev, dims) == points_length_cache[i]); +#else + length = len_vnvn(pt, pt_prev, dims); +#endif + + r_u[i] = r_u[i_prev] + length; + i_prev = i; + pt_prev = pt; + pt += dims; + } + assert(!is_almost_zero(r_u[points_offset_len - 1])); + const double w = r_u[points_offset_len - 1]; + for (uint i = 0; i < points_offset_len; i++) { + r_u[i] /= w; + } +} + +/** + * Use Newton-Raphson iteration to find better root. + * + * \param cubic: Current fitted curve. + * \param p: Point to test against. + * \param u: Parameter value for \a p. + * + * \note Return value may be `nan` caller must check for this. + */ +static double cubic_find_root( + const Cubic *cubic, + const double p[], + const double u, + const uint dims) +{ + /* Newton-Raphson Method. */ + /* all vectors */ +#ifdef USE_VLA + double q0_u[dims]; + double q1_u[dims]; + double q2_u[dims]; +#else + double *q0_u = alloca(sizeof(double) * dims); + double *q1_u = alloca(sizeof(double) * dims); + double *q2_u = alloca(sizeof(double) * dims); +#endif + + cubic_calc_point(cubic, u, dims, q0_u); + cubic_calc_speed(cubic, u, dims, q1_u); + cubic_calc_acceleration(cubic, u, dims, q2_u); + + /* may divide-by-zero, caller must check for that case */ + /* u - ((q0_u - p) * q1_u) / (q1_u.length_squared() + (q0_u - p) * q2_u) */ + isub_vnvn(q0_u, p, dims); + return u - dot_vnvn(q0_u, q1_u, dims) / + (len_squared_vn(q1_u, dims) + dot_vnvn(q0_u, q2_u, dims)); +} + +static int compare_double_fn(const void *a_, const void *b_) +{ + const double *a = a_; + const double *b = b_; + if (*a > *b) return 1; + else if (*a < *b) return -1; + else return 0; +} + +/** + * Given set of points and their parameterization, try to find a better parameterization. + */ +static bool cubic_reparameterize( + const Cubic *cubic, + const double *points_offset, + const uint points_offset_len, + const double *u, + const uint dims, + + double *r_u_prime) +{ + /* + * Recalculate the values of u[] based on the Newton Raphson method + */ + + const double *pt = points_offset; + for (uint i = 0; i < points_offset_len; i++, pt += dims) { + r_u_prime[i] = cubic_find_root(cubic, pt, u[i], dims); + if (!isfinite(r_u_prime[i])) { + return false; + } + } + + qsort(r_u_prime, points_offset_len, sizeof(double), compare_double_fn); + + if ((r_u_prime[0] < 0.0) || + (r_u_prime[points_offset_len - 1] > 1.0)) + { + return false; + } + + assert(r_u_prime[0] >= 0.0); + assert(r_u_prime[points_offset_len - 1] <= 1.0); + return true; +} + + +static void fit_cubic_to_points( + const double *points_offset, + const uint points_offset_len, +#ifdef USE_LENGTH_CACHE + const double *points_length_cache, +#endif + const double tan_l[], + const double tan_r[], + const double error_threshold, + const uint dims, + /* fill in the list */ + CubicList *clist) +{ + const uint iteration_max = 4; + const double error_sq = sq(error_threshold); + + Cubic *cubic; + + if (points_offset_len == 2) { + cubic = cubic_alloc(dims); + CUBIC_VARS(cubic, dims, p0, p1, p2, p3); + + copy_vnvn(p0, &points_offset[0 * dims], dims); + copy_vnvn(p3, &points_offset[1 * dims], dims); + + const double dist = len_vnvn(p0, p3, dims) / 3.0; + msub_vn_vnvn_fl(p1, p0, tan_l, dist, dims); + madd_vn_vnvn_fl(p2, p3, tan_r, dist, dims); + +#ifdef USE_ORIG_INDEX_DATA + cubic->orig_span = 1; +#endif + + cubic_list_prepend(clist, cubic); + return; + } + + double *u = malloc(sizeof(double) * points_offset_len); + points_calc_coord_length( + points_offset, points_offset_len, dims, +#ifdef USE_LENGTH_CACHE + points_length_cache, +#endif + u); + + cubic = cubic_alloc(dims); + + double error_sq_max; + uint split_index; + + /* Parameterize points, and attempt to fit curve */ + cubic_from_points( + points_offset, points_offset_len, u, tan_l, tan_r, dims, cubic); + + /* Find max deviation of points to fitted curve */ + cubic_calc_error( + cubic, points_offset, points_offset_len, u, dims, + &error_sq_max, &split_index); + + if (error_sq_max < error_sq) { + free(u); + cubic_list_prepend(clist, cubic); + return; + } + else { + /* If error not too large, try some reparameterization and iteration */ + double *u_prime = malloc(sizeof(double) * points_offset_len); + for (uint iter = 0; iter < iteration_max; iter++) { + if (!cubic_reparameterize( + cubic, points_offset, points_offset_len, u, dims, u_prime)) + { + break; + } + + cubic_from_points( + points_offset, points_offset_len, u_prime, + tan_l, tan_r, dims, cubic); + cubic_calc_error( + cubic, points_offset, points_offset_len, u_prime, dims, + &error_sq_max, &split_index); + + if (error_sq_max < error_sq) { + free(u_prime); + free(u); + cubic_list_prepend(clist, cubic); + return; + } + + SWAP(double *, u, u_prime); + } + free(u_prime); + } + + free(u); + cubic_free(cubic); + + + /* Fitting failed -- split at max error point and fit recursively */ + + /* Check splinePoint is not an endpoint? + * + * This assert happens sometimes... + * Look into it but disable for now. Campbell! */ + + // assert(split_index > 1) +#ifdef USE_VLA + double tan_center[dims]; +#else + double *tan_center = alloca(sizeof(double) * dims); +#endif + + const double *pt_a = &points_offset[(split_index - 1) * dims]; + const double *pt_b = &points_offset[(split_index + 1) * dims]; + + assert(split_index < points_offset_len); + if (equals_vnvn(pt_a, pt_b, dims)) { + pt_a += dims; + } + + /* tan_center = (pt_a - pt_b).normalized() */ + normalize_vn_vnvn(tan_center, pt_a, pt_b, dims); + + fit_cubic_to_points( + points_offset, split_index + 1, +#ifdef USE_LENGTH_CACHE + points_length_cache, +#endif + tan_l, tan_center, error_threshold, dims, clist); + fit_cubic_to_points( + &points_offset[split_index * dims], points_offset_len - split_index, +#ifdef USE_LENGTH_CACHE + points_length_cache + split_index, +#endif + tan_center, tan_r, error_threshold, dims, clist); + +} + +/** \} */ + + +/* -------------------------------------------------------------------- */ + +/** \name External API for Curve-Fitting + * \{ */ + +/** + * Main function: + * + * Take an array of 3d points. + * return the cubic splines + */ +int curve_fit_cubic_to_points_db( + const double *points, + const uint points_len, + const uint dims, + const double error_threshold, + const uint *corners, + uint corners_len, + + double **r_cubic_array, uint *r_cubic_array_len, + uint **r_cubic_orig_index, + uint **r_corner_index_array, uint *r_corner_index_len) +{ + uint corners_buf[2]; + if (corners == NULL) { + assert(corners_len == 0); + corners_buf[0] = 0; + corners_buf[1] = points_len - 1; + corners = corners_buf; + corners_len = 2; + } + + CubicList clist = {0}; + clist.dims = dims; + +#ifdef USE_VLA + double tan_l[dims]; + double tan_r[dims]; +#else + double *tan_l = alloca(sizeof(double) * dims); + double *tan_r = alloca(sizeof(double) * dims); +#endif + +#ifdef USE_LENGTH_CACHE + double *points_length_cache = NULL; + uint points_length_cache_len_alloc = 0; +#endif + + uint *corner_index_array = NULL; + uint corner_index = 0; + if (r_corner_index_array && (corners != corners_buf)) { + corner_index_array = malloc(sizeof(uint) * corners_len); + corner_index_array[corner_index++] = corners[0]; + } + + for (uint i = 1; i < corners_len; i++) { + const uint points_offset_len = corners[i] - corners[i - 1] + 1; + const uint first_point = corners[i - 1]; + + assert(points_offset_len >= 1); + if (points_offset_len > 1) { + const double *pt_l = &points[first_point * dims]; + const double *pt_r = &points[(first_point + points_offset_len - 1) * dims]; + const double *pt_l_next = pt_l + dims; + const double *pt_r_prev = pt_r - dims; + + /* tan_l = (pt_l - pt_l_next).normalized() + * tan_r = (pt_r_prev - pt_r).normalized() + */ + normalize_vn_vnvn(tan_l, pt_l, pt_l_next, dims); + normalize_vn_vnvn(tan_r, pt_r_prev, pt_r, dims); + +#ifdef USE_LENGTH_CACHE + if (points_length_cache_len_alloc < points_offset_len) { + if (points_length_cache) { + free(points_length_cache); + } + points_length_cache = malloc(sizeof(double) * points_offset_len); + } + points_calc_coord_length_cache( + &points[first_point * dims], points_offset_len, dims, + points_length_cache); +#endif + + fit_cubic_to_points( + &points[first_point * dims], points_offset_len, +#ifdef USE_LENGTH_CACHE + points_length_cache, +#endif + tan_l, tan_r, error_threshold, dims, &clist); + } + else if (points_len == 1) { + assert(points_offset_len == 1); + assert(corners_len == 2); + assert(corners[0] == 0); + assert(corners[1] == 0); + const double *pt = &points[0]; + Cubic *cubic = cubic_alloc(dims); + cubic_init(cubic, pt, pt, pt, pt, dims); + cubic_list_prepend(&clist, cubic); + } + + if (corner_index_array) { + corner_index_array[corner_index++] = clist.len; + } + } + +#ifdef USE_LENGTH_CACHE + if (points_length_cache) { + free(points_length_cache); + } +#endif + +#ifdef USE_ORIG_INDEX_DATA + uint *cubic_orig_index = NULL; + if (r_cubic_orig_index) { + cubic_orig_index = malloc(sizeof(uint) * (clist.len + 1)); + } +#else + *r_cubic_orig_index = NULL; +#endif + + /* allocate a contiguous array and free the linked list */ + *r_cubic_array = cubic_list_as_array( + &clist +#ifdef USE_ORIG_INDEX_DATA + , corners[corners_len - 1], cubic_orig_index +#endif + ); + *r_cubic_array_len = clist.len + 1; + + cubic_list_clear(&clist); + +#ifdef USE_ORIG_INDEX_DATA + if (cubic_orig_index) { + *r_cubic_orig_index = cubic_orig_index; + } +#endif + + if (corner_index_array) { + assert(corner_index == corners_len); + *r_corner_index_array = corner_index_array; + *r_corner_index_len = corner_index; + } + + return 0; +} + +/** + * A version of #curve_fit_cubic_to_points_db to handle floats + */ +int curve_fit_cubic_to_points_fl( + const float *points, + const uint points_len, + const uint dims, + const float error_threshold, + const uint *corners, + const uint corners_len, + + float **r_cubic_array, uint *r_cubic_array_len, + uint **r_cubic_orig_index, + uint **r_corner_index_array, uint *r_corner_index_len) +{ + const uint points_flat_len = points_len * dims; + double *points_db = malloc(sizeof(double) * points_flat_len); + + for (uint i = 0; i < points_flat_len; i++) { + points_db[i] = (double)points[i]; + } + + double *cubic_array_db = NULL; + float *cubic_array_fl = NULL; + uint cubic_array_len = 0; + + int result = curve_fit_cubic_to_points_db( + points_db, points_len, dims, error_threshold, corners, corners_len, + &cubic_array_db, &cubic_array_len, + r_cubic_orig_index, + r_corner_index_array, r_corner_index_len); + free(points_db); + + if (!result) { + uint cubic_array_flat_len = cubic_array_len * 3 * dims; + cubic_array_fl = malloc(sizeof(float) * cubic_array_flat_len); + for (uint i = 0; i < cubic_array_flat_len; i++) { + cubic_array_fl[i] = (float)cubic_array_db[i]; + } + free(cubic_array_db); + } + + *r_cubic_array = cubic_array_fl; + *r_cubic_array_len = cubic_array_len; + + return result; +} + +/** \} */ |