3482 lines
100 KiB
C
3482 lines
100 KiB
C
#define TINYSPLINE_EXPORT
|
|
#include "tinyspline.h"
|
|
#include "parson.h" /* serialization */
|
|
|
|
#include <stdlib.h> /* malloc, free */
|
|
#include <math.h> /* fabs, sqrt, acos */
|
|
#include <string.h> /* memcpy, memmove */
|
|
#include <stdio.h> /* FILE, fopen */
|
|
#include <stdarg.h> /* varargs */
|
|
|
|
/* Suppress some useless MSVC warnings. */
|
|
#ifdef _MSC_VER
|
|
#pragma warning(push)
|
|
/* address of dllimport */
|
|
#pragma warning(disable:4232)
|
|
/* function not inlined */
|
|
#pragma warning(disable:4710)
|
|
/* byte padding */
|
|
#pragma warning(disable:4820)
|
|
/* meaningless deprecation */
|
|
#pragma warning(disable:4996)
|
|
/* Spectre mitigation */
|
|
#pragma warning(disable:5045)
|
|
#endif
|
|
|
|
#define INIT_OUT_BSPLINE(in, out) \
|
|
if ((in) != (out)) \
|
|
ts_int_bspline_init(out);
|
|
|
|
|
|
|
|
/*! @name Internal Structs and Functions
|
|
*
|
|
* Internal functions are prefixed with \e ts_int (int for internal).
|
|
*
|
|
* @{
|
|
*/
|
|
/**
|
|
* Stores the private data of ::tsBSpline.
|
|
*/
|
|
struct tsBSplineImpl
|
|
{
|
|
size_t deg; /**< Degree of B-Spline basis function. */
|
|
size_t dim; /**< Dimensionality of the control points (2D => x, y). */
|
|
size_t n_ctrlp; /**< Number of control points. */
|
|
size_t n_knots; /**< Number of knots (n_ctrlp + deg + 1). */
|
|
};
|
|
|
|
/**
|
|
* Stores the private data of ::tsDeBoorNet.
|
|
*/
|
|
struct tsDeBoorNetImpl
|
|
{
|
|
tsReal u; /**< The evaluated knot. */
|
|
size_t k; /**< The index [u_k, u_k+1) */
|
|
size_t s; /**< Multiplicity of u_k. */
|
|
size_t h; /**< Number of insertions required to obtain result. */
|
|
size_t dim; /**< Dimensionality of the points (2D => x, y). */
|
|
size_t n_points; /** Number of points in `points'. */
|
|
};
|
|
|
|
void
|
|
ts_int_bspline_init(tsBSpline *spline)
|
|
{
|
|
spline->pImpl = NULL;
|
|
}
|
|
|
|
size_t
|
|
ts_int_bspline_sof_state(const tsBSpline *spline)
|
|
{
|
|
return sizeof(struct tsBSplineImpl) +
|
|
ts_bspline_sof_control_points(spline) +
|
|
ts_bspline_sof_knots(spline);
|
|
}
|
|
|
|
tsReal *
|
|
ts_int_bspline_access_ctrlp(const tsBSpline *spline)
|
|
{
|
|
return (tsReal *) (& spline->pImpl[1]);
|
|
}
|
|
|
|
tsReal *
|
|
ts_int_bspline_access_knots(const tsBSpline *spline)
|
|
{
|
|
return ts_int_bspline_access_ctrlp(spline) +
|
|
ts_bspline_len_control_points(spline);
|
|
}
|
|
|
|
tsError
|
|
ts_int_bspline_access_ctrlp_at(const tsBSpline *spline,
|
|
size_t index,
|
|
tsReal **ctrlp,
|
|
tsStatus *status)
|
|
{
|
|
const size_t num = ts_bspline_num_control_points(spline);
|
|
if (index >= num) {
|
|
TS_RETURN_2(status, TS_INDEX_ERROR,
|
|
"index (%lu) >= num(control_points) (%lu)",
|
|
(unsigned long) index,
|
|
(unsigned long) num)
|
|
}
|
|
*ctrlp = ts_int_bspline_access_ctrlp(spline) +
|
|
index * ts_bspline_dimension(spline);
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
tsError
|
|
ts_int_bspline_access_knot_at(const tsBSpline *spline,
|
|
size_t index,
|
|
tsReal *knot,
|
|
tsStatus *status)
|
|
{
|
|
const size_t num = ts_bspline_num_knots(spline);
|
|
if (index >= num) {
|
|
TS_RETURN_2(status, TS_INDEX_ERROR,
|
|
"index (%lu) >= num(knots) (%lu)",
|
|
(unsigned long) index,
|
|
(unsigned long) num)
|
|
}
|
|
*knot = ts_int_bspline_access_knots(spline)[index];
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
void
|
|
ts_int_deboornet_init(tsDeBoorNet *net)
|
|
{
|
|
net->pImpl = NULL;
|
|
}
|
|
|
|
size_t
|
|
ts_int_deboornet_sof_state(const tsDeBoorNet *net)
|
|
{
|
|
return sizeof(struct tsDeBoorNetImpl) +
|
|
ts_deboornet_sof_points(net) +
|
|
ts_deboornet_sof_result(net);
|
|
}
|
|
|
|
tsReal *
|
|
ts_int_deboornet_access_points(const tsDeBoorNet *net)
|
|
{
|
|
return (tsReal *) (& net->pImpl[1]);
|
|
}
|
|
|
|
tsReal *
|
|
ts_int_deboornet_access_result(const tsDeBoorNet *net)
|
|
{
|
|
if (ts_deboornet_num_result(net) == 2) {
|
|
return ts_int_deboornet_access_points(net);
|
|
} else {
|
|
return ts_int_deboornet_access_points(net) +
|
|
/* Last point in `points`. */
|
|
(ts_deboornet_len_points(net) -
|
|
ts_deboornet_dimension(net));
|
|
}
|
|
}
|
|
/*! @} */
|
|
|
|
|
|
|
|
/*! @name B-Spline Data
|
|
*
|
|
* @{
|
|
*/
|
|
size_t
|
|
ts_bspline_degree(const tsBSpline *spline)
|
|
{
|
|
return spline->pImpl->deg;
|
|
}
|
|
|
|
size_t
|
|
ts_bspline_order(const tsBSpline *spline)
|
|
{
|
|
return ts_bspline_degree(spline) + 1;
|
|
}
|
|
|
|
size_t
|
|
ts_bspline_dimension(const tsBSpline *spline)
|
|
{
|
|
return spline->pImpl->dim;
|
|
}
|
|
|
|
size_t
|
|
ts_bspline_len_control_points(const tsBSpline *spline)
|
|
{
|
|
return ts_bspline_num_control_points(spline) *
|
|
ts_bspline_dimension(spline);
|
|
}
|
|
|
|
size_t
|
|
ts_bspline_num_control_points(const tsBSpline *spline)
|
|
{
|
|
return spline->pImpl->n_ctrlp;
|
|
}
|
|
|
|
size_t
|
|
ts_bspline_sof_control_points(const tsBSpline *spline)
|
|
{
|
|
return ts_bspline_len_control_points(spline) * sizeof(tsReal);
|
|
}
|
|
|
|
const tsReal *
|
|
ts_bspline_control_points_ptr(const tsBSpline *spline)
|
|
{
|
|
return ts_int_bspline_access_ctrlp(spline);
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_control_points(const tsBSpline *spline,
|
|
tsReal **ctrlp,
|
|
tsStatus *status)
|
|
{
|
|
const size_t size = ts_bspline_sof_control_points(spline);
|
|
*ctrlp = (tsReal*) malloc(size);
|
|
if (!*ctrlp) TS_RETURN_0(status, TS_MALLOC, "out of memory")
|
|
memcpy(*ctrlp, ts_int_bspline_access_ctrlp(spline), size);
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_control_point_at_ptr(const tsBSpline *spline,
|
|
size_t index,
|
|
const tsReal **ctrlp,
|
|
tsStatus *status)
|
|
{
|
|
tsReal *vals;
|
|
tsError err;
|
|
TS_TRY(try, err, status)
|
|
TS_CALL(try, err, ts_int_bspline_access_ctrlp_at(
|
|
spline, index, &vals, status))
|
|
*ctrlp = vals;
|
|
TS_CATCH(err)
|
|
*ctrlp = NULL;
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_set_control_points(tsBSpline *spline,
|
|
const tsReal *ctrlp,
|
|
tsStatus *status)
|
|
{
|
|
const size_t size = ts_bspline_sof_control_points(spline);
|
|
memmove(ts_int_bspline_access_ctrlp(spline), ctrlp, size);
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_set_control_point_at(tsBSpline *spline,
|
|
size_t index,
|
|
const tsReal *ctrlp,
|
|
tsStatus *status)
|
|
{
|
|
tsReal *to;
|
|
size_t size;
|
|
tsError err;
|
|
TS_TRY(try, err, status)
|
|
TS_CALL(try, err, ts_int_bspline_access_ctrlp_at(
|
|
spline, index, &to, status))
|
|
size = ts_bspline_dimension(spline) * sizeof(tsReal);
|
|
memcpy(to, ctrlp, size);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
size_t
|
|
ts_bspline_num_knots(const tsBSpline *spline)
|
|
{
|
|
return spline->pImpl->n_knots;
|
|
}
|
|
|
|
size_t
|
|
ts_bspline_sof_knots(const tsBSpline *spline)
|
|
{
|
|
return ts_bspline_num_knots(spline) * sizeof(tsReal);
|
|
}
|
|
|
|
const tsReal *
|
|
ts_bspline_knots_ptr(const tsBSpline *spline)
|
|
{
|
|
return ts_int_bspline_access_knots(spline);
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_knots(const tsBSpline *spline,
|
|
tsReal **knots,
|
|
tsStatus *status)
|
|
{
|
|
const size_t size = ts_bspline_sof_knots(spline);
|
|
*knots = (tsReal*) malloc(size);
|
|
if (!*knots) TS_RETURN_0(status, TS_MALLOC, "out of memory")
|
|
memcpy(*knots, ts_int_bspline_access_knots(spline), size);
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_knot_at(const tsBSpline *spline,
|
|
size_t index,
|
|
tsReal *knot,
|
|
tsStatus *status)
|
|
{
|
|
return ts_int_bspline_access_knot_at(spline, index, knot, status);
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_set_knots(tsBSpline *spline,
|
|
const tsReal *knots,
|
|
tsStatus *status)
|
|
{
|
|
const size_t size = ts_bspline_sof_knots(spline);
|
|
const size_t num_knots = ts_bspline_num_knots(spline);
|
|
const size_t order = ts_bspline_order(spline);
|
|
size_t idx, mult;
|
|
tsReal lst_knot, knot;
|
|
lst_knot = knots[0];
|
|
mult = 1;
|
|
for (idx = 1; idx < num_knots; idx++) {
|
|
knot = knots[idx];
|
|
if (ts_knots_equal(lst_knot, knot)) {
|
|
mult++;
|
|
} else if (lst_knot > knot) {
|
|
TS_RETURN_1(status, TS_KNOTS_DECR,
|
|
"decreasing knot vector at index: %lu",
|
|
(unsigned long) idx)
|
|
} else {
|
|
mult = 0;
|
|
}
|
|
if (mult > order) {
|
|
TS_RETURN_3(status, TS_MULTIPLICITY,
|
|
"mult(%f) (%lu) > order (%lu)",
|
|
knot, (unsigned long) mult,
|
|
(unsigned long) order)
|
|
}
|
|
lst_knot = knot;
|
|
}
|
|
memmove(ts_int_bspline_access_knots(spline), knots, size);
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_set_knots_varargs(tsBSpline *spline,
|
|
tsStatus *status,
|
|
tsReal knot0,
|
|
double knot1,
|
|
...)
|
|
{
|
|
tsReal *values = NULL;
|
|
va_list argp;
|
|
size_t idx;
|
|
tsError err;
|
|
|
|
TS_TRY(try, err, status)
|
|
TS_CALL(try, err, ts_bspline_knots(
|
|
spline, &values, status))
|
|
|
|
values[0] = knot0;
|
|
values[1] = (tsReal) knot1;
|
|
va_start(argp, knot1);
|
|
for (idx = 2; idx < ts_bspline_num_knots(spline); idx++)
|
|
values[idx] = (tsReal) va_arg(argp, double);
|
|
va_end(argp);
|
|
|
|
TS_CALL(try, err, ts_bspline_set_knots(
|
|
spline, values, status))
|
|
TS_FINALLY
|
|
if (values) free(values);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_set_knot_at(tsBSpline *spline,
|
|
size_t index,
|
|
tsReal knot,
|
|
tsStatus *status)
|
|
{
|
|
tsError err;
|
|
tsReal *knots = NULL;
|
|
/* This is only for initialization. */
|
|
tsReal oldKnot = ts_int_bspline_access_knots(spline)[0];
|
|
TS_TRY(try, err, status)
|
|
TS_CALL(try, err, ts_int_bspline_access_knot_at(
|
|
spline, index, &oldKnot, status))
|
|
/* knots must be set after reading oldKnot because the catch
|
|
* block assumes that oldKnot contains the correct value if
|
|
* knots is not NULL. */
|
|
knots = ts_int_bspline_access_knots(spline);
|
|
knots[index] = knot;
|
|
TS_CALL(try, err, ts_bspline_set_knots(
|
|
spline, knots, status))
|
|
TS_CATCH(err)
|
|
/* If knots is not NULL, oldKnot contains the correct value. */
|
|
if (knots) knots[index] = oldKnot;
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
/*! @} */
|
|
|
|
|
|
|
|
/*! @name B-Spline Initialization
|
|
*
|
|
* @{
|
|
*/
|
|
tsBSpline
|
|
ts_bspline_init(void)
|
|
{
|
|
tsBSpline spline;
|
|
ts_int_bspline_init(&spline);
|
|
return spline;
|
|
}
|
|
|
|
tsError
|
|
ts_int_bspline_generate_knots(const tsBSpline *spline,
|
|
tsBSplineType type,
|
|
tsStatus *status)
|
|
{
|
|
const size_t n_knots = ts_bspline_num_knots(spline);
|
|
const size_t deg = ts_bspline_degree(spline);
|
|
const size_t order = ts_bspline_order(spline);
|
|
tsReal fac; /**< Factor used to calculate the knot values. */
|
|
size_t i; /**< Used in for loops. */
|
|
tsReal *knots; /**< Pointer to the knots of \p _result_. */
|
|
|
|
/* order >= 1 implies 2*order >= 2 implies n_knots >= 2 */
|
|
if (type == TS_BEZIERS && n_knots % order != 0) {
|
|
TS_RETURN_2(status, TS_NUM_KNOTS,
|
|
"num(knots) (%lu) %% order (%lu) != 0",
|
|
(unsigned long) n_knots, (unsigned long) order)
|
|
}
|
|
|
|
knots = ts_int_bspline_access_knots(spline);
|
|
|
|
if (type == TS_OPENED) {
|
|
knots[0] = TS_DOMAIN_DEFAULT_MIN; /* n_knots >= 2 */
|
|
fac = (TS_DOMAIN_DEFAULT_MAX - TS_DOMAIN_DEFAULT_MIN)
|
|
/ (n_knots - 1); /* n_knots >= 2 */
|
|
for (i = 1; i < n_knots-1; i++)
|
|
knots[i] = TS_DOMAIN_DEFAULT_MIN + i*fac;
|
|
knots[i] = TS_DOMAIN_DEFAULT_MAX; /* n_knots >= 2 */
|
|
} else if (type == TS_CLAMPED) {
|
|
/* n_knots >= 2*order == 2*(deg+1) == 2*deg + 2 > 2*deg - 1 */
|
|
fac = (TS_DOMAIN_DEFAULT_MAX - TS_DOMAIN_DEFAULT_MIN)
|
|
/ (n_knots - 2*deg - 1);
|
|
ts_arr_fill(knots, order, TS_DOMAIN_DEFAULT_MIN);
|
|
for (i = order ;i < n_knots-order; i++)
|
|
knots[i] = TS_DOMAIN_DEFAULT_MIN + (i-deg)*fac;
|
|
ts_arr_fill(knots + i, order, TS_DOMAIN_DEFAULT_MAX);
|
|
} else if (type == TS_BEZIERS) {
|
|
/* n_knots >= 2*order implies n_knots/order >= 2 */
|
|
fac = (TS_DOMAIN_DEFAULT_MAX - TS_DOMAIN_DEFAULT_MIN)
|
|
/ (n_knots/order - 1);
|
|
ts_arr_fill(knots, order, TS_DOMAIN_DEFAULT_MIN);
|
|
for (i = order; i < n_knots-order; i += order)
|
|
ts_arr_fill(knots + i,
|
|
order,
|
|
TS_DOMAIN_DEFAULT_MIN + (i/order)*fac);
|
|
ts_arr_fill(knots + i, order, TS_DOMAIN_DEFAULT_MAX);
|
|
}
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_new(size_t num_control_points,
|
|
size_t dimension,
|
|
size_t degree,
|
|
tsBSplineType type,
|
|
tsBSpline *spline,
|
|
tsStatus *status)
|
|
{
|
|
const size_t order = degree + 1;
|
|
const size_t num_knots = num_control_points + order;
|
|
const size_t len_ctrlp = num_control_points * dimension;
|
|
|
|
const size_t sof_real = sizeof(tsReal);
|
|
const size_t sof_impl = sizeof(struct tsBSplineImpl);
|
|
const size_t sof_ctrlp_vec = len_ctrlp * sof_real;
|
|
const size_t sof_knots_vec = num_knots * sof_real;
|
|
const size_t sof_spline = sof_impl + sof_ctrlp_vec + sof_knots_vec;
|
|
tsError err;
|
|
|
|
ts_int_bspline_init(spline);
|
|
|
|
if (dimension < 1) {
|
|
TS_RETURN_0(status, TS_DIM_ZERO, "unsupported dimension: 0")
|
|
}
|
|
if (num_knots > TS_MAX_NUM_KNOTS) {
|
|
TS_RETURN_2(status, TS_NUM_KNOTS,
|
|
"unsupported number of knots: %lu > %i",
|
|
(unsigned long) num_knots, TS_MAX_NUM_KNOTS)
|
|
}
|
|
if (degree >= num_control_points) {
|
|
TS_RETURN_2(status, TS_DEG_GE_NCTRLP,
|
|
"degree (%lu) >= num(control_points) (%lu)",
|
|
(unsigned long) degree,
|
|
(unsigned long) num_control_points)
|
|
}
|
|
|
|
spline->pImpl = (struct tsBSplineImpl *) malloc(sof_spline);
|
|
if (!spline->pImpl) TS_RETURN_0(status, TS_MALLOC, "out of memory")
|
|
|
|
spline->pImpl->deg = degree;
|
|
spline->pImpl->dim = dimension;
|
|
spline->pImpl->n_ctrlp = num_control_points;
|
|
spline->pImpl->n_knots = num_knots;
|
|
|
|
TS_TRY(try, err, status)
|
|
TS_CALL(try, err, ts_int_bspline_generate_knots(
|
|
spline, type, status))
|
|
TS_CATCH(err)
|
|
ts_bspline_free(spline);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_new_with_control_points(size_t num_control_points,
|
|
size_t dimension,
|
|
size_t degree,
|
|
tsBSplineType type,
|
|
tsBSpline *spline,
|
|
tsStatus *status,
|
|
double first,
|
|
...)
|
|
{
|
|
tsReal *ctrlp = NULL;
|
|
va_list argp;
|
|
size_t i;
|
|
tsError err;
|
|
|
|
TS_TRY(try, err, status)
|
|
TS_CALL(try, err, ts_bspline_new(
|
|
num_control_points, dimension,
|
|
degree, type, spline, status))
|
|
TS_CATCH(err)
|
|
ts_bspline_free(spline);
|
|
TS_END_TRY_ROE(err)
|
|
ctrlp = ts_int_bspline_access_ctrlp(spline);
|
|
|
|
ctrlp[0] = (tsReal) first;
|
|
va_start(argp, first);
|
|
for (i = 1; i < ts_bspline_len_control_points(spline); i++)
|
|
ctrlp[i] = (tsReal) va_arg(argp, double);
|
|
va_end(argp);
|
|
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_copy(const tsBSpline *src,
|
|
tsBSpline *dest,
|
|
tsStatus *status)
|
|
{
|
|
size_t size;
|
|
if (src == dest) TS_RETURN_SUCCESS(status)
|
|
ts_int_bspline_init(dest);
|
|
size = ts_int_bspline_sof_state(src);
|
|
dest->pImpl = (struct tsBSplineImpl *) malloc(size);
|
|
if (!dest->pImpl) TS_RETURN_0(status, TS_MALLOC, "out of memory")
|
|
memcpy(dest->pImpl, src->pImpl, size);
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
void
|
|
ts_bspline_move(tsBSpline *src,
|
|
tsBSpline *dest)
|
|
{
|
|
if (src == dest) return;
|
|
dest->pImpl = src->pImpl;
|
|
ts_int_bspline_init(src);
|
|
}
|
|
|
|
void
|
|
ts_bspline_free(tsBSpline *spline)
|
|
{
|
|
if (spline->pImpl) free(spline->pImpl);
|
|
ts_int_bspline_init(spline);
|
|
}
|
|
/*! @} */
|
|
|
|
|
|
|
|
/*! @name De Boor Net Data
|
|
*
|
|
* @{
|
|
*/
|
|
tsReal
|
|
ts_deboornet_knot(const tsDeBoorNet *net)
|
|
{
|
|
return net->pImpl->u;
|
|
}
|
|
|
|
size_t
|
|
ts_deboornet_index(const tsDeBoorNet *net)
|
|
{
|
|
return net->pImpl->k;
|
|
}
|
|
|
|
size_t
|
|
ts_deboornet_multiplicity(const tsDeBoorNet *net)
|
|
{
|
|
return net->pImpl->s;
|
|
}
|
|
|
|
size_t
|
|
ts_deboornet_num_insertions(const tsDeBoorNet *net)
|
|
{
|
|
return net->pImpl->h;
|
|
}
|
|
|
|
size_t
|
|
ts_deboornet_dimension(const tsDeBoorNet *net)
|
|
{
|
|
return net->pImpl->dim;
|
|
}
|
|
|
|
size_t
|
|
ts_deboornet_len_points(const tsDeBoorNet *net)
|
|
{
|
|
return ts_deboornet_num_points(net) *
|
|
ts_deboornet_dimension(net);
|
|
}
|
|
|
|
size_t
|
|
ts_deboornet_num_points(const tsDeBoorNet *net)
|
|
{
|
|
return net->pImpl->n_points;
|
|
}
|
|
|
|
size_t
|
|
ts_deboornet_sof_points(const tsDeBoorNet *net)
|
|
{
|
|
return ts_deboornet_len_points(net) * sizeof(tsReal);
|
|
}
|
|
|
|
const tsReal *
|
|
ts_deboornet_points_ptr(const tsDeBoorNet *net)
|
|
{
|
|
return ts_int_deboornet_access_points(net);
|
|
}
|
|
|
|
tsError
|
|
ts_deboornet_points(const tsDeBoorNet *net,
|
|
tsReal **points,
|
|
tsStatus *status)
|
|
{
|
|
const size_t size = ts_deboornet_sof_points(net);
|
|
*points = (tsReal*) malloc(size);
|
|
if (!*points) TS_RETURN_0(status, TS_MALLOC, "out of memory")
|
|
memcpy(*points, ts_int_deboornet_access_points(net), size);
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
size_t
|
|
ts_deboornet_len_result(const tsDeBoorNet *net)
|
|
{
|
|
return ts_deboornet_num_result(net) *
|
|
ts_deboornet_dimension(net);
|
|
}
|
|
|
|
size_t
|
|
ts_deboornet_num_result(const tsDeBoorNet *net)
|
|
{
|
|
return ts_deboornet_num_points(net) == 2 ? 2 : 1;
|
|
}
|
|
|
|
size_t
|
|
ts_deboornet_sof_result(const tsDeBoorNet *net)
|
|
{
|
|
return ts_deboornet_len_result(net) * sizeof(tsReal);
|
|
}
|
|
|
|
const tsReal *
|
|
ts_deboornet_result_ptr(const tsDeBoorNet *net)
|
|
{
|
|
return ts_int_deboornet_access_result(net);
|
|
}
|
|
|
|
tsError
|
|
ts_deboornet_result(const tsDeBoorNet *net,
|
|
tsReal **result,
|
|
tsStatus *status)
|
|
{
|
|
const size_t size = ts_deboornet_sof_result(net);
|
|
*result = (tsReal*) malloc(size);
|
|
if (!*result) TS_RETURN_0(status, TS_MALLOC, "out of memory")
|
|
memcpy(*result, ts_int_deboornet_access_result(net), size);
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
/*! @} */
|
|
|
|
|
|
|
|
/*! @name De Boor Net Initialization
|
|
*
|
|
* @{
|
|
*/
|
|
tsDeBoorNet
|
|
ts_deboornet_init(void)
|
|
{
|
|
tsDeBoorNet net;
|
|
ts_int_deboornet_init(&net);
|
|
return net;
|
|
}
|
|
|
|
tsError
|
|
ts_int_deboornet_new(const tsBSpline *spline,
|
|
tsDeBoorNet *net,
|
|
tsStatus *status)
|
|
{
|
|
const size_t dim = ts_bspline_dimension(spline);
|
|
const size_t deg = ts_bspline_degree(spline);
|
|
const size_t order = ts_bspline_order(spline);
|
|
const size_t num_points = (size_t)(order * (order+1) * 0.5f);
|
|
/* Handle `order == 1' which generates too few points. */
|
|
const size_t fixed_num_points = num_points < 2 ? 2 : num_points;
|
|
|
|
const size_t sof_real = sizeof(tsReal);
|
|
const size_t sof_impl = sizeof(struct tsDeBoorNetImpl);
|
|
const size_t sof_points_vec = fixed_num_points * dim * sof_real;
|
|
const size_t sof_net = sof_impl * sof_points_vec;
|
|
|
|
net->pImpl = (struct tsDeBoorNetImpl *) malloc(sof_net);
|
|
if (!net->pImpl) TS_RETURN_0(status, TS_MALLOC, "out of memory")
|
|
|
|
net->pImpl->u = 0.f;
|
|
net->pImpl->k = 0;
|
|
net->pImpl->s = 0;
|
|
net->pImpl->h = deg;
|
|
net->pImpl->dim = dim;
|
|
net->pImpl->n_points = fixed_num_points;
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
void
|
|
ts_deboornet_free(tsDeBoorNet *net)
|
|
{
|
|
if (net->pImpl) free(net->pImpl);
|
|
ts_int_deboornet_init(net);
|
|
}
|
|
|
|
tsError
|
|
ts_deboornet_copy(const tsDeBoorNet *src,
|
|
tsDeBoorNet *dest,
|
|
tsStatus *status)
|
|
{
|
|
size_t size;
|
|
if (src == dest) TS_RETURN_SUCCESS(status)
|
|
ts_int_deboornet_init(dest);
|
|
size = ts_int_deboornet_sof_state(src);
|
|
dest->pImpl = (struct tsDeBoorNetImpl *) malloc(size);
|
|
if (!dest->pImpl) TS_RETURN_0(status, TS_MALLOC, "out of memory")
|
|
memcpy(dest->pImpl, src->pImpl, size);
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
void
|
|
ts_deboornet_move(tsDeBoorNet *src,
|
|
tsDeBoorNet *dest)
|
|
{
|
|
if (src == dest) return;
|
|
dest->pImpl = src->pImpl;
|
|
ts_int_deboornet_init(src);
|
|
}
|
|
/*! @} */
|
|
|
|
|
|
|
|
/*! @name Interpolation and Approximation Functions
|
|
*
|
|
* @{
|
|
*/
|
|
tsError
|
|
ts_int_cubic_point(const tsReal *point,
|
|
size_t dim,
|
|
tsBSpline *spline,
|
|
tsStatus *status)
|
|
{
|
|
const size_t size = dim * sizeof(tsReal);
|
|
tsReal *ctrlp = NULL;
|
|
size_t i;
|
|
tsError err;
|
|
TS_CALL_ROE(err, ts_bspline_new(
|
|
4, dim, 3,
|
|
TS_CLAMPED, spline, status))
|
|
ctrlp = ts_int_bspline_access_ctrlp(spline);
|
|
for (i = 0; i < 4; i++) {
|
|
memcpy(ctrlp + i*dim,
|
|
point,
|
|
size);
|
|
}
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
tsError
|
|
ts_int_thomas_algorithm(const tsReal *a,
|
|
const tsReal *b,
|
|
const tsReal *c,
|
|
size_t num,
|
|
size_t dim,
|
|
tsReal *d,
|
|
tsStatus *status)
|
|
{
|
|
size_t i, j, k, l;
|
|
tsReal m, *cc = NULL;
|
|
tsError err;
|
|
|
|
if (dim == 0) {
|
|
TS_RETURN_0(status, TS_DIM_ZERO,
|
|
"unsupported dimension: 0")
|
|
}
|
|
if (num <= 1) {
|
|
TS_RETURN_1(status, TS_NUM_POINTS,
|
|
"num(points) (%lu) <= 1",
|
|
(unsigned long) num)
|
|
}
|
|
cc = (tsReal *) malloc(num * sizeof(tsReal));
|
|
if (!cc) TS_RETURN_0(status, TS_MALLOC, "out of memory")
|
|
|
|
TS_TRY(try, err, status)
|
|
/* Forward sweep. */
|
|
if (fabs(b[0]) <= fabs(c[0])) {
|
|
TS_THROW_2(try, err, status, TS_NO_RESULT,
|
|
"error: |%f| <= |%f|", b[0], c[0])
|
|
}
|
|
/* |b[i]| > |c[i]| implies that |b[i]| > 0. Thus, the following
|
|
* statements cannot evaluate to division by zero.*/
|
|
cc[0] = c[0] / b[0];
|
|
for (i = 0; i < dim; i++)
|
|
d[i] = d[i] / b[0];
|
|
for (i = 1; i < num; i++) {
|
|
if (fabs(b[i]) <= fabs(a[i]) + fabs(c[i])) {
|
|
TS_THROW_3(try, err, status, TS_NO_RESULT,
|
|
"error: |%f| <= |%f| + |%f|",
|
|
b[i], a[i], c[i])
|
|
}
|
|
/* |a[i]| < |b[i]| and cc[i - 1] < 1. Therefore, the
|
|
* following statement cannot evaluate to division by
|
|
* zero. */
|
|
m = 1.f / (b[i] - a[i] * cc[i - 1]);
|
|
/* |b[i]| > |a[i]| + |c[i]| implies that there must be
|
|
* an eps > 0 such that |b[i]| = |a[i]| + |c[i]| + eps.
|
|
* Even if |a[i]| is 0 (by which the result of the
|
|
* following statement becomes maximum), |c[i]| is less
|
|
* than |b[i]| by an amount of eps. By substituting the
|
|
* previous and the following statements (under the
|
|
* assumption that |a[i]| is 0), we obtain c[i] / b[i],
|
|
* which must be less than 1. */
|
|
cc[i] = c[i] * m;
|
|
for (j = 0; j < dim; j++) {
|
|
k = i * dim + j;
|
|
l = (i-1) * dim + j;
|
|
d[k] = (d[k] - a[i] * d[l]) * m;
|
|
}
|
|
}
|
|
|
|
/* Back substitution. */
|
|
for (i = num-1; i > 0; i--) {
|
|
for (j = 0; j < dim; j++) {
|
|
k = (i-1) * dim + j;
|
|
l = i * dim + j;
|
|
d[k] -= cc[i-1] * d[l];
|
|
}
|
|
}
|
|
TS_FINALLY
|
|
free(cc);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
tsError
|
|
ts_int_relaxed_uniform_cubic_bspline(const tsReal *points,
|
|
size_t n,
|
|
size_t dim,
|
|
tsBSpline *spline,
|
|
tsStatus *status)
|
|
{
|
|
const size_t order = 4; /**< Order of spline to interpolate. */
|
|
const tsReal as = 1.f/6.f; /**< The value 'a sixth'. */
|
|
const tsReal at = 1.f/3.f; /**< The value 'a third'. */
|
|
const tsReal tt = 2.f/3.f; /**< The value 'two third'. */
|
|
size_t sof_ctrlp; /**< Size of a single control point. */
|
|
const tsReal* b = points; /**< Array of the b values. */
|
|
tsReal* s; /**< Array of the s values. */
|
|
size_t i, d; /**< Used in for loops */
|
|
size_t j, k, l; /**< Used as temporary indices. */
|
|
tsReal *ctrlp; /**< Pointer to the control points of \p _spline_. */
|
|
tsError err;
|
|
|
|
/* input validation */
|
|
if (dim == 0)
|
|
TS_RETURN_0(status, TS_DIM_ZERO, "unsupported dimension: 0")
|
|
if (n <= 1) {
|
|
TS_RETURN_1(status, TS_NUM_POINTS,
|
|
"num(points) (%lu) <= 1",
|
|
(unsigned long) n)
|
|
}
|
|
/* in the following n >= 2 applies */
|
|
|
|
sof_ctrlp = dim * sizeof(tsReal); /* dim > 0 implies sof_ctrlp > 0 */
|
|
|
|
s = NULL;
|
|
TS_TRY(try, err, status)
|
|
/* n >= 2 implies n-1 >= 1 implies (n-1)*4 >= 4 */
|
|
TS_CALL(try, err, ts_bspline_new(
|
|
(n-1) * 4, dim, order - 1,
|
|
TS_BEZIERS, spline, status))
|
|
ctrlp = ts_int_bspline_access_ctrlp(spline);
|
|
|
|
s = (tsReal*) malloc(n * sof_ctrlp);
|
|
if (!s) {
|
|
TS_THROW_0(try, err, status, TS_MALLOC,
|
|
"out of memory")
|
|
}
|
|
|
|
/* set s_0 to b_0 and s_n = b_n */
|
|
memcpy(s, b, sof_ctrlp);
|
|
memcpy(s + (n-1)*dim, b + (n-1)*dim, sof_ctrlp);
|
|
|
|
/* set s_i = 1/6*b_i + 2/3*b_{i-1} + 1/6*b_{i+1}*/
|
|
for (i = 1; i < n-1; i++) {
|
|
for (d = 0; d < dim; d++) {
|
|
j = (i-1)*dim+d;
|
|
k = i*dim+d;
|
|
l = (i+1)*dim+d;
|
|
s[k] = as * b[j];
|
|
s[k] += tt * b[k];
|
|
s[k] += as * b[l];
|
|
}
|
|
}
|
|
|
|
/* create beziers from b and s */
|
|
for (i = 0; i < n-1; i++) {
|
|
for (d = 0; d < dim; d++) {
|
|
j = i*dim+d;
|
|
k = i*4*dim+d;
|
|
l = (i+1)*dim+d;
|
|
ctrlp[k] = s[j];
|
|
ctrlp[k+dim] = tt*b[j] + at*b[l];
|
|
ctrlp[k+2*dim] = at*b[j] + tt*b[l];
|
|
ctrlp[k+3*dim] = s[l];
|
|
}
|
|
}
|
|
TS_CATCH(err)
|
|
ts_bspline_free(spline);
|
|
TS_FINALLY
|
|
if (s)
|
|
free(s);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_interpolate_cubic_natural(const tsReal *points,
|
|
size_t num_points,
|
|
size_t dimension,
|
|
tsBSpline *spline,
|
|
tsStatus *status)
|
|
{
|
|
const size_t sof_ctrlp = dimension * sizeof(tsReal);
|
|
const size_t len_points = num_points * dimension;
|
|
const size_t num_int_points = num_points - 2;
|
|
const size_t len_int_points = num_int_points * dimension;
|
|
tsReal *thomas, *a, *b, *c, *d;
|
|
size_t i, j, k, l;
|
|
tsError err;
|
|
|
|
ts_int_bspline_init(spline);
|
|
if (num_points == 0)
|
|
TS_RETURN_0(status, TS_NUM_POINTS, "num(points) == 0")
|
|
if (num_points == 1) {
|
|
TS_CALL_ROE(err, ts_int_cubic_point(
|
|
points, dimension, spline, status))
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
if (num_points == 2) {
|
|
return ts_int_relaxed_uniform_cubic_bspline(
|
|
points, num_points, dimension, spline, status);
|
|
}
|
|
/* `num_points` >= 3 */
|
|
thomas = NULL;
|
|
TS_TRY(try, err, status)
|
|
thomas = (tsReal *) malloc(
|
|
/* `a', `b', `c' (note that `c' is equal to `a') */
|
|
2 * num_int_points * sizeof(tsReal) +
|
|
/* `d' and "result of the thomas algorithm" (which
|
|
contains `num_points' points) */
|
|
num_points * dimension * sizeof(tsReal));
|
|
if (!thomas) {
|
|
TS_THROW_0(try, err, status, TS_MALLOC,
|
|
"out of memory")
|
|
}
|
|
/* The system of linear equations is taken from:
|
|
* http://www.bakoma-tex.com/doc/generic/pst-bspline/
|
|
* pst-bspline-doc.pdf */
|
|
a = c = thomas;
|
|
ts_arr_fill(a, num_int_points, 1);
|
|
b = a + num_int_points;
|
|
ts_arr_fill(b, num_int_points, 4);
|
|
d = b + num_int_points;
|
|
/* 6 * S_{i+1} */
|
|
for (i = 0; i < num_int_points; i++) {
|
|
for (j = 0; j < dimension; j++) {
|
|
k = i * dimension + j;
|
|
l = (i+1) * dimension + j;
|
|
d[k] = 6 * points[l];
|
|
}
|
|
}
|
|
for (i = 0; i < dimension; i++) {
|
|
/* 6 * S_{1} - S_{0} */
|
|
d[i] -= points[i];
|
|
/* 6 * S_{n-1} - S_{n} */
|
|
k = len_int_points - (i+1);
|
|
l = len_points - (i+1);
|
|
d[k] -= points[l];
|
|
}
|
|
/* The Thomas algorithm requires at least two points. Hence,
|
|
* `num_int_points` == 1 must be handled separately (let's call
|
|
* it "Mini Thomas"). */
|
|
if (num_int_points == 1) {
|
|
for (i = 0; i < dimension; i++)
|
|
d[i] *= (tsReal) 0.25f;
|
|
} else {
|
|
TS_CALL(try, err, ts_int_thomas_algorithm(
|
|
a, b, c, num_int_points, dimension, d,
|
|
status))
|
|
}
|
|
memcpy(thomas, points, sof_ctrlp);
|
|
memmove(thomas + dimension, d, num_int_points * sof_ctrlp);
|
|
memcpy(thomas + (num_int_points+1) * dimension,
|
|
points + (num_points-1) * dimension, sof_ctrlp);
|
|
TS_CALL(try, err, ts_int_relaxed_uniform_cubic_bspline(
|
|
thomas, num_points, dimension, spline, status))
|
|
TS_CATCH(err)
|
|
ts_bspline_free(spline);
|
|
TS_FINALLY
|
|
if (thomas)
|
|
free(thomas);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_interpolate_catmull_rom(const tsReal *points,
|
|
size_t num_points,
|
|
size_t dimension,
|
|
tsReal alpha,
|
|
const tsReal *first,
|
|
const tsReal *last,
|
|
tsReal epsilon,
|
|
tsBSpline *spline,
|
|
tsStatus *status)
|
|
{
|
|
const size_t sof_real = sizeof(tsReal);
|
|
const size_t sof_ctrlp = dimension * sof_real;
|
|
const tsReal eps = (tsReal) fabs(epsilon);
|
|
tsReal *bs_ctrlp; /* Points to the control points of `spline`. */
|
|
tsReal *cr_ctrlp; /**< The points to interpolate based on `points`. */
|
|
size_t i, d; /**< Used in for loops. */
|
|
tsError err; /**< Local error handling. */
|
|
/* [https://en.wikipedia.org/wiki/
|
|
* Centripetal_Catmull%E2%80%93Rom_spline] */
|
|
tsReal t0, t1, t2, t3; /**< Catmull-Rom knots. */
|
|
/* [https://stackoverflow.com/questions/30748316/
|
|
* catmull-rom-interpolation-on-svg-paths/30826434#30826434] */
|
|
tsReal c1, c2, d1, d2, m1, m2; /**< Used to calculate derivatives. */
|
|
tsReal *p0, *p1, *p2, *p3; /**< Processed Catmull-Rom points. */
|
|
|
|
ts_int_bspline_init(spline);
|
|
if (dimension == 0)
|
|
TS_RETURN_0(status, TS_DIM_ZERO, "unsupported dimension: 0")
|
|
if (num_points == 0)
|
|
TS_RETURN_0(status, TS_NUM_POINTS, "num(points) == 0")
|
|
if (alpha < (tsReal) 0.0) alpha = (tsReal) 0.0;
|
|
if (alpha > (tsReal) 1.0) alpha = (tsReal) 1.0;
|
|
|
|
/* Copy `points` to `cr_ctrlp`. Add space for `first` and `last`. */
|
|
cr_ctrlp = (tsReal *) malloc((num_points + 2) * sof_ctrlp);
|
|
if (!cr_ctrlp)
|
|
TS_RETURN_0(status, TS_MALLOC, "out of memory")
|
|
memcpy(cr_ctrlp + dimension, points, num_points * sof_ctrlp);
|
|
|
|
/* Remove redundant points from `cr_ctrlp`. Update `num_points`. */
|
|
for (i = 1 /* 0 (`first`) is not assigned yet */;
|
|
i < num_points /* skip last point (inclusive end) */;
|
|
i++) {
|
|
p0 = cr_ctrlp + (i * dimension);
|
|
p1 = p0 + dimension;
|
|
if (ts_distance(p0, p1, dimension) <= eps) {
|
|
/* Are there any other points (after the one that is
|
|
* to be removed) that need to be shifted? */
|
|
if (i < num_points - 1) {
|
|
memmove(p1, p1 + dimension,
|
|
(num_points - (i + 1)) * sof_ctrlp);
|
|
}
|
|
num_points--;
|
|
i--;
|
|
}
|
|
}
|
|
|
|
/* Check if there are still enough points for interpolation. */
|
|
if (num_points == 1) { /* `num_points` can't be 0 */
|
|
free(cr_ctrlp); /* The point is copied from `points`. */
|
|
TS_CALL_ROE(err, ts_int_cubic_point(
|
|
points, dimension, spline, status))
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
/* Add or generate `first` and `last`. Update `num_points`. */
|
|
p0 = cr_ctrlp + dimension;
|
|
if (first && ts_distance(first, p0, dimension) > eps) {
|
|
memcpy(cr_ctrlp, first, sof_ctrlp);
|
|
} else {
|
|
p1 = p0 + dimension;
|
|
for (d = 0; d < dimension; d++)
|
|
cr_ctrlp[d] = p0[d] + (p0[d] - p1[d]);
|
|
}
|
|
p1 = cr_ctrlp + (num_points * dimension);
|
|
if (last && ts_distance(p1, last, dimension) > eps) {
|
|
memcpy(cr_ctrlp + ((num_points + 1) * dimension),
|
|
last, sof_ctrlp);
|
|
} else {
|
|
p0 = p1 - dimension;
|
|
for (d = 0; d < dimension; d++) {
|
|
cr_ctrlp[((num_points + 1) * dimension) + d] =
|
|
p1[d] + (p1[d] - p0[d]);
|
|
}
|
|
}
|
|
num_points = num_points + 2;
|
|
|
|
/* Transform the sequence of Catmull-Rom splines. */
|
|
bs_ctrlp = NULL;
|
|
TS_TRY(try, err, status)
|
|
TS_CALL(try, err, ts_bspline_new(
|
|
(num_points - 3) * 4, dimension, 3,
|
|
TS_BEZIERS, spline, status))
|
|
bs_ctrlp = ts_int_bspline_access_ctrlp(spline);
|
|
TS_CATCH(err)
|
|
free(cr_ctrlp);
|
|
TS_END_TRY_ROE(err)
|
|
for (i = 0; i < ts_bspline_num_control_points(spline) / 4; i++) {
|
|
p0 = cr_ctrlp + ((i+0) * dimension);
|
|
p1 = cr_ctrlp + ((i+1) * dimension);
|
|
p2 = cr_ctrlp + ((i+2) * dimension);
|
|
p3 = cr_ctrlp + ((i+3) * dimension);
|
|
|
|
t0 = (tsReal) 0.f;
|
|
t1 = t0 + (tsReal) pow(ts_distance(p0, p1, dimension), alpha);
|
|
t2 = t1 + (tsReal) pow(ts_distance(p1, p2, dimension), alpha);
|
|
t3 = t2 + (tsReal) pow(ts_distance(p2, p3, dimension), alpha);
|
|
|
|
c1 = (t2-t1) / (t2-t0);
|
|
c2 = (t1-t0) / (t2-t0);
|
|
d1 = (t3-t2) / (t3-t1);
|
|
d2 = (t2-t1) / (t3-t1);
|
|
|
|
for (d = 0; d < dimension; d++) {
|
|
m1 = (t2-t1)*(c1*(p1[d]-p0[d])/(t1-t0)
|
|
+ c2*(p2[d]-p1[d])/(t2-t1));
|
|
m2 = (t2-t1)*(d1*(p2[d]-p1[d])/(t2-t1)
|
|
+ d2*(p3[d]-p2[d])/(t3-t2));
|
|
bs_ctrlp[((i*4 + 0) * dimension) + d] = p1[d];
|
|
bs_ctrlp[((i*4 + 1) * dimension) + d] = p1[d] + m1/3;
|
|
bs_ctrlp[((i*4 + 2) * dimension) + d] = p2[d] - m2/3;
|
|
bs_ctrlp[((i*4 + 3) * dimension) + d] = p2[d];
|
|
}
|
|
}
|
|
free(cr_ctrlp);
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
/*! @} */
|
|
|
|
|
|
|
|
/*! @name Query Functions
|
|
*
|
|
* @{
|
|
*/
|
|
tsError
|
|
ts_int_bspline_find_knot(const tsBSpline *spline,
|
|
tsReal *knot, /* in: knot; out: actual knot */
|
|
size_t *idx, /* out: index of `knot' */
|
|
size_t *mult, /* out: multiplicity of `knot' */
|
|
tsStatus *status)
|
|
{
|
|
const size_t deg = ts_bspline_degree(spline);
|
|
const size_t num_knots = ts_bspline_num_knots(spline);
|
|
const tsReal *knots = ts_int_bspline_access_knots(spline);
|
|
tsReal min, max;
|
|
size_t low, high;
|
|
|
|
ts_bspline_domain(spline, &min, &max);
|
|
if (*knot < min) {
|
|
/* Avoid infinite loop (issue #222) */
|
|
if (ts_knots_equal(*knot, min)) *knot = min;
|
|
else {
|
|
TS_RETURN_2(status, TS_U_UNDEFINED,
|
|
"knot (%f) < min(domain) (%f)",
|
|
*knot, min)
|
|
}
|
|
}
|
|
else if (*knot > max && !ts_knots_equal(*knot, max)) {
|
|
TS_RETURN_2(status, TS_U_UNDEFINED,
|
|
"knot (%f) > max(domain) (%f)",
|
|
*knot, max)
|
|
}
|
|
|
|
/* Based on 'The NURBS Book' (Les Piegl and Wayne Tiller). */
|
|
if (ts_knots_equal(*knot, knots[num_knots - 1])) {
|
|
*idx = num_knots - 1;
|
|
} else {
|
|
low = 0;
|
|
high = num_knots - 1;
|
|
*idx = (low+high) / 2;
|
|
while (*knot < knots[*idx] || *knot >= knots[*idx + 1]) {
|
|
if (*knot < knots[*idx])
|
|
high = *idx;
|
|
else
|
|
low = *idx;
|
|
*idx = (low+high) / 2;
|
|
}
|
|
}
|
|
|
|
/* Handle floating point errors. */
|
|
while (*idx < num_knots - 1 && /* there is a next knot */
|
|
ts_knots_equal(*knot, knots[*idx + 1])) {
|
|
(*idx)++;
|
|
}
|
|
if (ts_knots_equal(*knot, knots[*idx]))
|
|
*knot = knots[*idx]; /* set actual knot */
|
|
|
|
/* Calculate knot's multiplicity. */
|
|
for (*mult = deg + 1; *mult > 0 ; (*mult)--) {
|
|
if (ts_knots_equal(*knot, knots[*idx - (*mult-1)]))
|
|
break;
|
|
}
|
|
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
tsError
|
|
ts_int_bspline_eval_woa(const tsBSpline *spline,
|
|
tsReal u,
|
|
tsDeBoorNet *net,
|
|
tsStatus *status)
|
|
{
|
|
const size_t deg = ts_bspline_degree(spline);
|
|
const size_t order = ts_bspline_order(spline);
|
|
const size_t dim = ts_bspline_dimension(spline);
|
|
const size_t num_knots = ts_bspline_num_knots(spline);
|
|
const size_t sof_ctrlp = dim * sizeof(tsReal);
|
|
|
|
const tsReal *ctrlp = ts_int_bspline_access_ctrlp(spline);
|
|
const tsReal *knots = ts_int_bspline_access_knots(spline);
|
|
tsReal *points = NULL; /**< Pointer to the points of \p net. */
|
|
|
|
size_t k; /**< Index of \p u. */
|
|
size_t s; /**< Multiplicity of \p u. */
|
|
|
|
size_t from; /**< Offset used to copy values. */
|
|
size_t fst; /**< First affected control point, inclusive. */
|
|
size_t lst; /**< Last affected control point, inclusive. */
|
|
size_t N; /**< Number of affected control points. */
|
|
|
|
/* The following indices are used to create the DeBoor net. */
|
|
size_t lidx; /**< Current left index. */
|
|
size_t ridx; /**< Current right index. */
|
|
size_t tidx; /**< Current to index. */
|
|
size_t r, i, d; /**< Used in for loop. */
|
|
tsReal ui; /**< Knot value at index i. */
|
|
tsReal a, a_hat; /**< Weighting factors of control points. */
|
|
|
|
tsError err;
|
|
|
|
points = ts_int_deboornet_access_points(net);
|
|
|
|
/* 1. Find index k such that u is in between [u_k, u_k+1).
|
|
* 2. Setup already known values.
|
|
* 3. Decide by multiplicity of u how to calculate point P(u). */
|
|
|
|
/* 1. */
|
|
k = s = 0;
|
|
TS_CALL_ROE(err, ts_int_bspline_find_knot(
|
|
spline, &u, &k, &s, status))
|
|
|
|
/* 2. */
|
|
net->pImpl->u = u;
|
|
net->pImpl->k = k;
|
|
net->pImpl->s = s;
|
|
net->pImpl->h = deg < s ? 0 : deg-s; /* prevent underflow */
|
|
|
|
/* 3. (by 1. s <= order)
|
|
*
|
|
* 3a) Check for s = order.
|
|
* Take the two points k-s and k-s + 1. If one of
|
|
* them doesn't exist, take only the other.
|
|
* 3b) Use de boor algorithm to find point P(u). */
|
|
if (s == order) {
|
|
/* only one of the two control points exists */
|
|
if (k == deg || /* only the first */
|
|
k == num_knots - 1) { /* only the last */
|
|
from = k == deg ? 0 : (k-s) * dim;
|
|
net->pImpl->n_points = 1;
|
|
memcpy(points, ctrlp + from, sof_ctrlp);
|
|
} else {
|
|
from = (k-s) * dim;
|
|
net->pImpl->n_points = 2;
|
|
memcpy(points, ctrlp + from, 2 * sof_ctrlp);
|
|
}
|
|
} else { /* by 3a) s <= deg (order = deg+1) */
|
|
fst = k-deg; /* by 1. k >= deg */
|
|
lst = k-s; /* s <= deg <= k */
|
|
N = lst-fst + 1; /* lst <= fst implies N >= 1 */
|
|
|
|
net->pImpl->n_points = (size_t)(N * (N+1) * 0.5f);
|
|
|
|
/* copy initial values to output */
|
|
memcpy(points, ctrlp + fst*dim, N * sof_ctrlp);
|
|
|
|
lidx = 0;
|
|
ridx = dim;
|
|
tidx = N*dim; /* N >= 1 implies tidx > 0 */
|
|
r = 1;
|
|
for (;r <= ts_deboornet_num_insertions(net); r++) {
|
|
i = fst + r;
|
|
for (; i <= lst; i++) {
|
|
ui = knots[i];
|
|
a = (ts_deboornet_knot(net) - ui) /
|
|
(knots[i+deg-r+1] - ui);
|
|
a_hat = 1.f-a;
|
|
|
|
for (d = 0; d < dim; d++) {
|
|
points[tidx++] =
|
|
a_hat * points[lidx++] +
|
|
a * points[ridx++];
|
|
}
|
|
}
|
|
lidx += dim;
|
|
ridx += dim;
|
|
}
|
|
}
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_eval(const tsBSpline *spline,
|
|
tsReal knot,
|
|
tsDeBoorNet *net,
|
|
tsStatus *status)
|
|
{
|
|
tsError err;
|
|
ts_int_deboornet_init(net);
|
|
TS_TRY(try, err, status)
|
|
TS_CALL(try, err, ts_int_deboornet_new(
|
|
spline, net, status))
|
|
TS_CALL(try, err, ts_int_bspline_eval_woa(
|
|
spline, knot, net, status))
|
|
TS_CATCH(err)
|
|
ts_deboornet_free(net);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_eval_all(const tsBSpline *spline,
|
|
const tsReal *knots,
|
|
size_t num,
|
|
tsReal **points,
|
|
tsStatus *status)
|
|
{
|
|
const size_t dim = ts_bspline_dimension(spline);
|
|
const size_t sof_point = dim * sizeof(tsReal);
|
|
const size_t sof_points = num * sof_point;
|
|
tsDeBoorNet net = ts_deboornet_init();
|
|
tsReal *result;
|
|
size_t i;
|
|
tsError err;
|
|
TS_TRY(try, err, status)
|
|
*points = (tsReal *) malloc(sof_points);
|
|
if (!*points) {
|
|
TS_THROW_0(try, err, status, TS_MALLOC,
|
|
"out of memory")
|
|
}
|
|
TS_CALL(try, err, ts_int_deboornet_new(
|
|
spline,&net, status))
|
|
for (i = 0; i < num; i++) {
|
|
TS_CALL(try, err, ts_int_bspline_eval_woa(
|
|
spline, knots[i], &net, status))
|
|
result = ts_int_deboornet_access_result(&net);
|
|
memcpy((*points) + i * dim, result, sof_point);
|
|
}
|
|
TS_CATCH(err)
|
|
if (*points)
|
|
free(*points);
|
|
*points = NULL;
|
|
TS_FINALLY
|
|
ts_deboornet_free(&net);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_sample(const tsBSpline *spline,
|
|
size_t num,
|
|
tsReal **points,
|
|
size_t *actual_num,
|
|
tsStatus *status)
|
|
{
|
|
tsError err;
|
|
tsReal *knots;
|
|
|
|
num = num == 0 ? 100 : num;
|
|
*actual_num = num;
|
|
knots = (tsReal *) malloc(num * sizeof(tsReal));
|
|
if (!knots) {
|
|
*points = NULL;
|
|
TS_RETURN_0(status, TS_MALLOC, "out of memory")
|
|
}
|
|
ts_bspline_uniform_knot_seq(spline, num, knots);
|
|
TS_TRY(try, err, status)
|
|
TS_CALL(try, err, ts_bspline_eval_all(
|
|
spline, knots, num, points, status))
|
|
TS_FINALLY
|
|
free(knots);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_bisect(const tsBSpline *spline,
|
|
tsReal value,
|
|
tsReal epsilon,
|
|
int persnickety,
|
|
size_t index,
|
|
int ascending,
|
|
size_t max_iter,
|
|
tsDeBoorNet *net,
|
|
tsStatus *status)
|
|
{
|
|
tsError err;
|
|
const size_t dim = ts_bspline_dimension(spline);
|
|
const tsReal eps = (tsReal) fabs(epsilon);
|
|
size_t i = 0;
|
|
tsReal dist = 0;
|
|
tsReal min, max, mid;
|
|
tsReal *P;
|
|
|
|
ts_int_deboornet_init(net);
|
|
|
|
if (dim < index) {
|
|
TS_RETURN_2(status, TS_INDEX_ERROR,
|
|
"dimension (%lu) <= index (%lu)",
|
|
(unsigned long) dim,
|
|
(unsigned long) index)
|
|
}
|
|
if(max_iter == 0)
|
|
TS_RETURN_0(status, TS_NO_RESULT, "0 iterations")
|
|
|
|
ts_bspline_domain(spline, &min, &max);
|
|
TS_TRY(try, err, status)
|
|
TS_CALL(try, err, ts_int_deboornet_new(
|
|
spline, net, status))
|
|
do {
|
|
mid = (tsReal) ((min + max) / 2.0);
|
|
TS_CALL(try, err, ts_int_bspline_eval_woa(
|
|
spline, mid, net, status))
|
|
P = ts_int_deboornet_access_result(net);
|
|
dist = ts_distance(&P[index], &value, 1);
|
|
if (dist <= eps)
|
|
TS_RETURN_SUCCESS(status)
|
|
if (ascending) {
|
|
if (P[index] < value)
|
|
min = mid;
|
|
else
|
|
max = mid;
|
|
} else {
|
|
if (P[index] < value)
|
|
max = mid;
|
|
else
|
|
min = mid;
|
|
}
|
|
} while (i++ < max_iter);
|
|
if (persnickety) {
|
|
TS_THROW_1(try, err, status, TS_NO_RESULT,
|
|
"maximum iterations (%lu) exceeded",
|
|
(unsigned long) max_iter)
|
|
}
|
|
TS_CATCH(err)
|
|
ts_deboornet_free(net);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
void ts_bspline_domain(const tsBSpline *spline,
|
|
tsReal *min,
|
|
tsReal *max)
|
|
{
|
|
*min = ts_int_bspline_access_knots(spline)
|
|
[ts_bspline_degree(spline)];
|
|
*max = ts_int_bspline_access_knots(spline)
|
|
[ts_bspline_num_knots(spline) - ts_bspline_order(spline)];
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_is_closed(const tsBSpline *spline,
|
|
tsReal epsilon,
|
|
int *closed,
|
|
tsStatus *status)
|
|
{
|
|
const size_t deg = ts_bspline_degree(spline);
|
|
const size_t dim = ts_bspline_dimension(spline);
|
|
tsBSpline derivative;
|
|
tsReal min, max;
|
|
tsDeBoorNet first, last;
|
|
size_t i;
|
|
tsError err;
|
|
|
|
ts_int_bspline_init(&derivative);
|
|
ts_int_deboornet_init(&first);
|
|
ts_int_deboornet_init(&last);
|
|
|
|
TS_TRY(try, err, status)
|
|
for (i = 0; i < deg; i++) {
|
|
TS_CALL(try, err, ts_bspline_derive(
|
|
spline, i, -1.f, &derivative, status))
|
|
ts_bspline_domain(&derivative, &min, &max);
|
|
TS_CALL(try, err, ts_bspline_eval(
|
|
&derivative, min, &first, status))
|
|
TS_CALL(try, err, ts_bspline_eval(
|
|
&derivative, max, &last, status))
|
|
*closed = ts_distance(
|
|
ts_int_deboornet_access_result(&first),
|
|
ts_int_deboornet_access_result(&last),
|
|
dim) <= epsilon ? 1 : 0;
|
|
ts_bspline_free(&derivative);
|
|
ts_deboornet_free(&first);
|
|
ts_deboornet_free(&last);
|
|
if (!*closed)
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
TS_FINALLY
|
|
ts_bspline_free(&derivative);
|
|
ts_deboornet_free(&first);
|
|
ts_deboornet_free(&last);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_compute_rmf(const tsBSpline *spline,
|
|
const tsReal *knots,
|
|
size_t num,
|
|
int has_first_normal,
|
|
tsFrame *frames,
|
|
tsStatus *status)
|
|
{
|
|
tsError err;
|
|
size_t i;
|
|
tsReal fx, fy, fz, fmin;
|
|
tsReal xc[3], xn[3], v1[3], c1, v2[3], c2, rL[3], tL[3];
|
|
tsBSpline deriv = ts_bspline_init();
|
|
tsDeBoorNet curr = ts_deboornet_init();
|
|
tsDeBoorNet next = ts_deboornet_init();
|
|
|
|
if (num < 1)
|
|
TS_RETURN_SUCCESS(status);
|
|
|
|
TS_TRY(try, err, status)
|
|
TS_CALL(try, err, ts_int_deboornet_new(
|
|
spline, &curr, status))
|
|
TS_CALL(try, err, ts_int_deboornet_new(
|
|
spline, &next, status))
|
|
TS_CALL(try, err, ts_bspline_derive(
|
|
spline, 1, (tsReal) -1.0, &deriv, status))
|
|
|
|
/* Set position. */
|
|
TS_CALL(try, err, ts_int_bspline_eval_woa(
|
|
spline, knots[0], &curr, status))
|
|
ts_vec3_set(frames[0].position,
|
|
ts_int_deboornet_access_result(&curr),
|
|
ts_bspline_dimension(spline));
|
|
/* Set tangent. */
|
|
TS_CALL(try, err, ts_int_bspline_eval_woa(
|
|
&deriv, knots[0], &curr, status))
|
|
ts_vec3_set(frames[0].tangent,
|
|
ts_int_deboornet_access_result(&curr),
|
|
ts_bspline_dimension(&deriv));
|
|
ts_vec_norm(frames[0].tangent, 3, frames[0].tangent);
|
|
/* Set normal. */
|
|
if (!has_first_normal) {
|
|
fx = (tsReal) fabs(frames[0].tangent[0]);
|
|
fy = (tsReal) fabs(frames[0].tangent[1]);
|
|
fz = (tsReal) fabs(frames[0].tangent[2]);
|
|
fmin = fx; /* x is min => 1, 0, 0 */
|
|
ts_vec3_init(frames[0].normal,
|
|
(tsReal) 1.0,
|
|
(tsReal) 0.0,
|
|
(tsReal) 0.0);
|
|
if (fy < fmin) { /* y is min => 0, 1, 0 */
|
|
fmin = fy;
|
|
ts_vec3_init(frames[0].normal,
|
|
(tsReal) 0.0,
|
|
(tsReal) 1.0,
|
|
(tsReal) 0.0);
|
|
}
|
|
if (fz < fmin) { /* z is min => 0, 0, 1 */
|
|
ts_vec3_init(frames[0].normal,
|
|
(tsReal) 0.0,
|
|
(tsReal) 0.0,
|
|
(tsReal) 1.0);
|
|
}
|
|
ts_vec3_cross(frames[0].tangent,
|
|
frames[0].normal,
|
|
frames[0].normal);
|
|
ts_vec_norm(frames[0].normal, 3, frames[0].normal);
|
|
if (ts_bspline_dimension(spline) >= 3) {
|
|
/* In 3D (and higher) an additional rotation of
|
|
the normal along the tangent is needed in
|
|
order to let the normal extend sideways (as
|
|
it does in 2D and lower). */
|
|
ts_vec3_cross(frames[0].tangent,
|
|
frames[0].normal,
|
|
frames[0].normal);
|
|
}
|
|
} else {
|
|
/* Never trust user input! */
|
|
ts_vec_norm(frames[0].normal, 3, frames[0].normal);
|
|
}
|
|
/* Set binormal. */
|
|
ts_vec3_cross(frames[0].tangent,
|
|
frames[0].normal,
|
|
frames[0].binormal);
|
|
|
|
for (i = 0; i < num - 1; i++) {
|
|
/* Eval current and next point. */
|
|
TS_CALL(try, err, ts_int_bspline_eval_woa(
|
|
spline, knots[i], &curr, status))
|
|
TS_CALL(try, err, ts_int_bspline_eval_woa(
|
|
spline, knots[i+1], &next, status))
|
|
ts_vec3_set(xc, /* xc is now the current point */
|
|
ts_int_deboornet_access_result(&curr),
|
|
ts_bspline_dimension(spline));
|
|
ts_vec3_set(xn, /* xn is now the next point */
|
|
ts_int_deboornet_access_result(&next),
|
|
ts_bspline_dimension(spline));
|
|
|
|
/* Set position of U_{i+1}. */
|
|
ts_vec3_set(frames[i+1].position, xn, 3);
|
|
|
|
/* Compute reflection vector of R_{1}. */
|
|
ts_vec_sub(xn, xc, 3, v1);
|
|
c1 = ts_vec_dot(v1, v1, 3);
|
|
|
|
/* Compute r_{i}^{L} = R_{1} * r_{i}. */
|
|
rL[0] = (tsReal) 2.0 / c1;
|
|
rL[1] = ts_vec_dot(v1, frames[i].normal, 3);
|
|
rL[2] = rL[0] * rL[1];
|
|
ts_vec_mul(v1, 3, rL[2], rL);
|
|
ts_vec_sub(frames[i].normal, rL, 3, rL);
|
|
|
|
/* Compute t_{i}^{L} = R_{1} * t_{i}. */
|
|
tL[0] = (tsReal) 2.0 / c1;
|
|
tL[1] = ts_vec_dot(v1, frames[i].tangent, 3);
|
|
tL[2] = tL[0] * tL[1];
|
|
ts_vec_mul(v1, 3, tL[2], tL);
|
|
ts_vec_sub(frames[i].tangent, tL, 3, tL);
|
|
|
|
/* Compute reflection vector of R_{2}. */
|
|
TS_CALL(try, err, ts_int_bspline_eval_woa(
|
|
&deriv, knots[i+1], &next, status))
|
|
ts_vec3_set(xn, /* xn is now the next tangent */
|
|
ts_int_deboornet_access_result(&next),
|
|
ts_bspline_dimension(&deriv));
|
|
ts_vec_norm(xn, 3, xn);
|
|
ts_vec_sub(xn, tL, 3, v2);
|
|
c2 = ts_vec_dot(v2, v2, 3);
|
|
|
|
/* Compute r_{i+1} = R_{2} * r_{i}^{L}. */
|
|
ts_vec3_set(xc, /* xc is now the next normal */
|
|
frames[i+1].normal, 3);
|
|
xc[0] = (tsReal) 2.0 / c2;
|
|
xc[1] = ts_vec_dot(v2, rL, 3);
|
|
xc[2] = xc[0] * xc[1];
|
|
ts_vec_mul(v2, 3, xc[2], xc);
|
|
ts_vec_sub(rL, xc, 3, xc);
|
|
ts_vec_norm(xc, 3, xc);
|
|
|
|
/* Compute vector s_{i+1} of U_{i+1}. */
|
|
ts_vec3_cross(xn, xc, frames[i+1].binormal);
|
|
|
|
/* Set vectors t_{i+1} and r_{i+1} of U_{i+1}. */
|
|
ts_vec3_set(frames[i+1].tangent, xn, 3);
|
|
ts_vec3_set(frames[i+1].normal, xc, 3);
|
|
}
|
|
TS_FINALLY
|
|
ts_bspline_free(&deriv);
|
|
ts_deboornet_free(&curr);
|
|
ts_deboornet_free(&next);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
|
|
tsError
|
|
ts_bspline_chord_lengths(const tsBSpline *spline,
|
|
const tsReal *knots,
|
|
size_t num,
|
|
tsReal *lengths,
|
|
tsStatus *status)
|
|
{
|
|
tsError err;
|
|
tsReal dist, lst_knot, cur_knot;
|
|
size_t i, dim = ts_bspline_dimension(spline);
|
|
tsDeBoorNet lst = ts_deboornet_init();
|
|
tsDeBoorNet cur = ts_deboornet_init();
|
|
tsDeBoorNet tmp = ts_deboornet_init();
|
|
|
|
if (num == 0) TS_RETURN_SUCCESS(status);
|
|
|
|
TS_TRY(try, err, status)
|
|
TS_CALL(try, err, ts_int_deboornet_new(
|
|
spline, &lst, status))
|
|
TS_CALL(try, err, ts_int_deboornet_new(
|
|
spline, &cur, status))
|
|
|
|
/* num >= 1 */
|
|
TS_CALL(try, err, ts_int_bspline_eval_woa(
|
|
spline, knots[0], &lst, status));
|
|
lengths[0] = (tsReal) 0.0;
|
|
|
|
for (i = 1; i < num; i++) {
|
|
TS_CALL(try, err, ts_int_bspline_eval_woa(
|
|
spline, knots[i], &cur, status));
|
|
lst_knot = ts_deboornet_knot(&lst);
|
|
cur_knot = ts_deboornet_knot(&cur);
|
|
if (cur_knot < lst_knot) {
|
|
TS_THROW_1(try, err, status, TS_KNOTS_DECR,
|
|
"decreasing knot at index: %lu",
|
|
(unsigned long) i)
|
|
}
|
|
dist = ts_distance(ts_deboornet_result_ptr(&lst),
|
|
ts_deboornet_result_ptr(&cur),
|
|
dim);
|
|
lengths[i] = lengths[i-1] + dist;
|
|
ts_deboornet_move(&lst, &tmp);
|
|
ts_deboornet_move(&cur, &lst);
|
|
ts_deboornet_move(&tmp, &cur);
|
|
}
|
|
TS_FINALLY
|
|
ts_deboornet_free(&lst);
|
|
ts_deboornet_free(&cur);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
|
|
tsError
|
|
ts_bspline_sub_spline(const tsBSpline *spline,
|
|
tsReal knot0,
|
|
tsReal knot1,
|
|
tsBSpline *sub,
|
|
tsStatus *status)
|
|
{
|
|
int reverse; /* reverse `spline`? (if `knot0 > knot1`) */
|
|
tsReal *tmp = NULL; /* a buffer to swap control points */
|
|
tsReal min, max; /* domain of `spline` */
|
|
size_t dim, deg, order; /* properties of `spline` (and `sub`) */
|
|
tsBSpline worker; /* stores the result of the `split' operations */
|
|
tsReal *ctrlp, *knots; /* control points and knots of `worker` */
|
|
size_t k0, k1; /* indices returned by the `split' operations */
|
|
size_t c0, c1; /* indices of the control points to be moved */
|
|
size_t nc, nk; /* number of control points and knots of `sub` */
|
|
size_t i; /* for various needs */
|
|
tsError err; /* for local try-catch block */
|
|
|
|
/* Make sure that `worker` points to `NULL'. This allows us to call
|
|
* `ts_bspline_free` in `TS_CATCH` without further checks. Also, `NULL'
|
|
* serves as an indicator of whether `ts_bspline_split` has been called
|
|
* on `spline` at least once (if not, `worker` needs to be initialized
|
|
* manually). */
|
|
ts_int_bspline_init(&worker);
|
|
INIT_OUT_BSPLINE(spline, sub)
|
|
|
|
ts_bspline_domain(spline, &min, &max);
|
|
dim = ts_bspline_dimension(spline);
|
|
deg = ts_bspline_degree(spline);
|
|
order = ts_bspline_order(spline);
|
|
|
|
/* Cannot create valid knot vector from empty domain. */
|
|
if (ts_knots_equal(knot0, knot1)) {
|
|
TS_RETURN_0(status,
|
|
TS_NO_RESULT,
|
|
"empty domain")
|
|
}
|
|
|
|
/* Check for `reverse mode'. Reverse mode means that the copied sequence
|
|
* of (sub) control points need to be reversed, forming a `backwards'
|
|
* spline. */
|
|
reverse = knot0 > knot1;
|
|
if (reverse) { /* swap `knot0` and `knot1` */
|
|
tmp = (tsReal *) malloc(dim * sizeof(tsReal));
|
|
if (!tmp) TS_RETURN_0(status, TS_MALLOC, "out of memory");
|
|
*tmp = knot0; /* `tmp` can hold at least one value */
|
|
knot0 = knot1;
|
|
knot1 = *tmp;
|
|
}
|
|
|
|
TS_TRY(try, err, status)
|
|
if (!ts_knots_equal(knot0 , min)) {
|
|
TS_CALL(try , err, ts_bspline_split(
|
|
spline, knot0, &worker, &k0, status))
|
|
} else { k0 = deg; }
|
|
if (!ts_knots_equal(knot1, max)) {
|
|
TS_CALL(try , err, ts_bspline_split(
|
|
/* If `NULL', the split operation
|
|
above was not called. */
|
|
!worker.pImpl ? spline : &worker,
|
|
knot1, &worker, &k1, status))
|
|
} else {
|
|
k1 = ts_bspline_num_knots(
|
|
/* If `NULL', the split operation
|
|
above was not called. */
|
|
!worker.pImpl ? spline : &worker) - 1;
|
|
}
|
|
|
|
/* Set up `worker`. */
|
|
if (!worker.pImpl) { /* => no split applied */
|
|
TS_CALL(try, err, ts_bspline_copy(
|
|
spline, &worker, status))
|
|
/* Needed in `reverse mode'. */
|
|
ctrlp = ts_int_bspline_access_ctrlp(&worker);
|
|
knots = ts_int_bspline_access_knots(&worker);
|
|
nc = ts_bspline_num_control_points(&worker);
|
|
} else {
|
|
c0 = (k0-deg) * dim;
|
|
c1 = (k1-order) * dim;
|
|
nc = ((c1-c0) / dim) + 1;
|
|
nk = (k1-k0) + order;
|
|
|
|
/* Also needed in `reverse mode'. */
|
|
ctrlp = ts_int_bspline_access_ctrlp(&worker);
|
|
knots = ts_int_bspline_access_knots(&worker);
|
|
|
|
/* Move control points. */
|
|
memmove(ctrlp,
|
|
ctrlp + c0,
|
|
nc * dim * sizeof(tsReal));
|
|
/* Move knots. */
|
|
memmove(ctrlp + nc * dim,
|
|
knots + (k0-deg),
|
|
nk * sizeof(tsReal));
|
|
|
|
/* Remove superfluous control points and knots from
|
|
* the memory of `worker`. */
|
|
worker.pImpl->n_knots = nk;
|
|
worker.pImpl->n_ctrlp = nc;
|
|
i = ts_int_bspline_sof_state(&worker);
|
|
worker.pImpl = realloc(worker.pImpl, i);
|
|
if (worker.pImpl == NULL) { /* unlikely to fail */
|
|
TS_THROW_0(try, err, status, TS_MALLOC,
|
|
"out of memory")
|
|
}
|
|
}
|
|
|
|
/* Reverse control points (if necessary). */
|
|
if (reverse) {
|
|
for (i = 0; i < nc / 2; i++) {
|
|
memcpy(tmp,
|
|
ctrlp + i * dim,
|
|
dim * sizeof(tsReal));
|
|
memmove(ctrlp + i * dim,
|
|
ctrlp + (nc-1 - i) * dim,
|
|
dim * sizeof(tsReal));
|
|
memcpy(ctrlp + (nc-1 - i) * dim,
|
|
tmp,
|
|
dim * sizeof(tsReal));
|
|
}
|
|
}
|
|
|
|
/* Move `worker' to output parameter. */
|
|
if (spline == sub) ts_bspline_free(sub);
|
|
ts_bspline_move(&worker, sub);
|
|
TS_CATCH(err)
|
|
ts_bspline_free(&worker);
|
|
TS_FINALLY
|
|
if (tmp) free(tmp);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
void
|
|
ts_bspline_uniform_knot_seq(const tsBSpline *spline,
|
|
size_t num,
|
|
tsReal *knots)
|
|
{
|
|
size_t i;
|
|
tsReal min, max;
|
|
if (num == 0) return;
|
|
ts_bspline_domain(spline, &min, &max);
|
|
for (i = 0; i < num; i++) {
|
|
knots[i] = max - min;
|
|
knots[i] *= (tsReal) i / (num - 1);
|
|
knots[i] += min;
|
|
}
|
|
/* Set `knots[0]` after `knots[num - 1]` to ensure
|
|
that `knots[0] = min` if `num` is `1'. */
|
|
knots[num - 1] = max;
|
|
knots[0] = min;
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_equidistant_knot_seq(const tsBSpline *spline,
|
|
size_t num,
|
|
tsReal *knots,
|
|
size_t num_samples,
|
|
tsStatus *status)
|
|
{
|
|
tsError err;
|
|
tsReal *samples = NULL, *lengths = NULL;
|
|
|
|
if (num == 0) TS_RETURN_SUCCESS(status);
|
|
if (num_samples == 0) num_samples = 200;
|
|
|
|
samples = (tsReal *) malloc(2 * num_samples * sizeof(tsReal));
|
|
if (!samples) TS_RETURN_0(status, TS_MALLOC, "out of memory");
|
|
ts_bspline_uniform_knot_seq(spline, num_samples, samples);
|
|
lengths = samples + num_samples;
|
|
TS_TRY(try, err, status)
|
|
TS_CALL(try, err, ts_bspline_chord_lengths(
|
|
spline, samples, num_samples, lengths, status))
|
|
TS_CALL(try, err, ts_chord_lengths_equidistant_knot_seq(
|
|
samples, lengths, num_samples, num, knots, status))
|
|
TS_FINALLY
|
|
free(samples); /* cannot be NULL */
|
|
/* free(lengths); NO! */
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
/*! @} */
|
|
|
|
|
|
|
|
/*! @name Transformation Functions
|
|
*
|
|
* @{
|
|
*/
|
|
tsError
|
|
ts_int_bspline_resize(const tsBSpline *spline,
|
|
int n,
|
|
int back,
|
|
tsBSpline *resized,
|
|
tsStatus *status)
|
|
{
|
|
const size_t deg = ts_bspline_degree(spline);
|
|
const size_t dim = ts_bspline_dimension(spline);
|
|
const size_t sof_real = sizeof(tsReal);
|
|
|
|
const size_t num_ctrlp = ts_bspline_num_control_points(spline);
|
|
const size_t num_knots = ts_bspline_num_knots(spline);
|
|
const size_t nnum_ctrlp = num_ctrlp + n; /**< New length of ctrlp. */
|
|
const size_t nnum_knots = num_knots + n; /**< New length of knots. */
|
|
const size_t min_num_ctrlp_vec = n < 0 ? nnum_ctrlp : num_ctrlp;
|
|
const size_t min_num_knots_vec = n < 0 ? nnum_knots : num_knots;
|
|
|
|
const size_t sof_min_num_ctrlp = min_num_ctrlp_vec * dim * sof_real;
|
|
const size_t sof_min_num_knots = min_num_knots_vec * sof_real;
|
|
|
|
tsBSpline tmp; /**< Temporarily stores the result. */
|
|
const tsReal* from_ctrlp = ts_int_bspline_access_ctrlp(spline);
|
|
const tsReal* from_knots = ts_int_bspline_access_knots(spline);
|
|
tsReal* to_ctrlp = NULL; /**< Pointer to the control points of tmp. */
|
|
tsReal* to_knots = NULL; /**< Pointer to the knots of tmp. */
|
|
|
|
tsError err;
|
|
|
|
if (n == 0) return ts_bspline_copy(spline, resized, status);
|
|
|
|
INIT_OUT_BSPLINE(spline, resized)
|
|
TS_CALL_ROE(err, ts_bspline_new(
|
|
nnum_ctrlp, dim, deg, TS_OPENED,
|
|
&tmp, status))
|
|
to_ctrlp = ts_int_bspline_access_ctrlp(&tmp);
|
|
to_knots = ts_int_bspline_access_knots(&tmp);
|
|
|
|
/* Copy control points and knots. */
|
|
if (!back && n < 0) {
|
|
memcpy(to_ctrlp, from_ctrlp - n*dim, sof_min_num_ctrlp);
|
|
memcpy(to_knots, from_knots - n , sof_min_num_knots);
|
|
} else if (!back && n > 0) {
|
|
memcpy(to_ctrlp + n*dim, from_ctrlp, sof_min_num_ctrlp);
|
|
memcpy(to_knots + n , from_knots, sof_min_num_knots);
|
|
} else {
|
|
/* n != 0 implies back == true */
|
|
memcpy(to_ctrlp, from_ctrlp, sof_min_num_ctrlp);
|
|
memcpy(to_knots, from_knots, sof_min_num_knots);
|
|
}
|
|
|
|
if (spline == resized)
|
|
ts_bspline_free(resized);
|
|
ts_bspline_move(&tmp, resized);
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_derive(const tsBSpline *spline,
|
|
size_t n,
|
|
tsReal epsilon,
|
|
tsBSpline *deriv,
|
|
tsStatus *status)
|
|
{
|
|
const size_t sof_real = sizeof(tsReal);
|
|
const size_t dim = ts_bspline_dimension(spline);
|
|
const size_t sof_ctrlp = dim * sof_real;
|
|
size_t deg = ts_bspline_degree(spline);
|
|
size_t num_ctrlp = ts_bspline_num_control_points(spline);
|
|
size_t num_knots = ts_bspline_num_knots(spline);
|
|
|
|
tsBSpline worker; /**< Stores the intermediate result. */
|
|
tsReal* ctrlp; /**< Pointer to the control points of worker. */
|
|
tsReal* knots; /**< Pointer to the knots of worker. */
|
|
|
|
size_t m, i, j, k, l; /**< Used in for loops. */
|
|
tsReal *fst, *snd; /**< Pointer to first and second control point. */
|
|
tsReal dist; /**< Distance between fst and snd. */
|
|
tsReal kid1, ki1; /**< Knots at i+deg+1 and i+1. */
|
|
tsReal span; /**< Distance between kid1 and ki1. */
|
|
|
|
tsBSpline swap; /**< Used to swap worker and derivative. */
|
|
tsError err;
|
|
|
|
INIT_OUT_BSPLINE(spline, deriv)
|
|
TS_CALL_ROE(err, ts_bspline_copy(spline, &worker, status))
|
|
ctrlp = ts_int_bspline_access_ctrlp(&worker);
|
|
knots = ts_int_bspline_access_knots(&worker);
|
|
|
|
TS_TRY(try, err, status)
|
|
for (m = 1; m <= n; m++) { /* from 1st to n'th derivative */
|
|
if (deg == 0) {
|
|
ts_arr_fill(ctrlp, dim, 0.f);
|
|
ts_bspline_domain(spline,
|
|
&knots[0],
|
|
&knots[1]);
|
|
num_ctrlp = 1;
|
|
num_knots = 2;
|
|
break;
|
|
}
|
|
/* Check and, if possible, fix discontinuity. */
|
|
for (i = 2*deg + 1; i < num_knots - (deg+1); i++) {
|
|
if (!ts_knots_equal(knots[i], knots[i-deg]))
|
|
continue;
|
|
fst = ctrlp + (i - (deg+1)) * dim;
|
|
snd = fst + dim;
|
|
dist = ts_distance(fst, snd, dim);
|
|
if (epsilon >= 0.f && dist > epsilon) {
|
|
TS_THROW_1(try, err, status,
|
|
TS_UNDERIVABLE,
|
|
"discontinuity at knot: %f",
|
|
knots[i])
|
|
}
|
|
memmove(snd,
|
|
snd + dim,
|
|
(num_ctrlp - (i+1-deg)) * sof_ctrlp);
|
|
memmove(&knots[i],
|
|
&knots[i+1],
|
|
(num_knots - (i+1)) * sof_real);
|
|
num_ctrlp--;
|
|
num_knots--;
|
|
i += deg-1;
|
|
}
|
|
/* Derive continuous worker. */
|
|
for (i = 0; i < num_ctrlp-1; i++) {
|
|
for (j = 0; j < dim; j++) {
|
|
k = i *dim + j;
|
|
l = (i+1)*dim + j;
|
|
ctrlp[k] = ctrlp[l] - ctrlp[k];
|
|
kid1 = knots[i+deg+1];
|
|
ki1 = knots[i+1];
|
|
span = kid1 - ki1;
|
|
if (span < TS_KNOT_EPSILON)
|
|
span = TS_KNOT_EPSILON;
|
|
ctrlp[k] *= deg;
|
|
ctrlp[k] /= span;
|
|
}
|
|
}
|
|
deg -= 1;
|
|
num_ctrlp -= 1;
|
|
num_knots -= 2;
|
|
knots += 1;
|
|
}
|
|
TS_CALL(try, err, ts_bspline_new(
|
|
num_ctrlp, dim, deg, TS_OPENED,
|
|
&swap, status))
|
|
memcpy(ts_int_bspline_access_ctrlp(&swap),
|
|
ctrlp,
|
|
num_ctrlp * sof_ctrlp);
|
|
memcpy(ts_int_bspline_access_knots(&swap),
|
|
knots,
|
|
num_knots * sof_real);
|
|
if (spline == deriv)
|
|
ts_bspline_free(deriv);
|
|
ts_bspline_move(&swap, deriv);
|
|
TS_FINALLY
|
|
ts_bspline_free(&worker);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
tsError
|
|
ts_int_bspline_insert_knot(const tsBSpline *spline,
|
|
const tsDeBoorNet *net,
|
|
size_t n,
|
|
tsBSpline *result,
|
|
tsStatus *status)
|
|
{
|
|
const size_t deg = ts_bspline_degree(spline);
|
|
const size_t order = ts_bspline_order(spline);
|
|
const size_t dim = ts_bspline_dimension(spline);
|
|
const tsReal knot = ts_deboornet_knot(net);
|
|
const size_t k = ts_deboornet_index(net);
|
|
const size_t mult = ts_deboornet_multiplicity(net);
|
|
const size_t sof_real = sizeof(tsReal);
|
|
const size_t sof_ctrlp = dim * sof_real;
|
|
|
|
size_t N; /**< Number of affected control points. */
|
|
tsReal* from; /**< Pointer to copy the values from. */
|
|
tsReal* to; /**< Pointer to copy the values to. */
|
|
int stride; /**< Stride of the next pointer to copy. */
|
|
size_t i; /**< Used in for loops. */
|
|
|
|
tsReal *ctrlp_spline, *ctrlp_result;
|
|
tsReal *knots_spline, *knots_result;
|
|
size_t num_ctrlp_result;
|
|
size_t num_knots_result;
|
|
|
|
tsError err;
|
|
|
|
INIT_OUT_BSPLINE(spline, result)
|
|
if (n == 0)
|
|
return ts_bspline_copy(spline, result, status);
|
|
if (mult + n > order) {
|
|
TS_RETURN_4(status, TS_MULTIPLICITY,
|
|
"multiplicity(%f) (%lu) + %lu > order (%lu)",
|
|
knot, (unsigned long) mult, (unsigned long) n,
|
|
(unsigned long) order)
|
|
}
|
|
|
|
TS_CALL_ROE(err, ts_int_bspline_resize(
|
|
spline, (int)n, 1, result, status))
|
|
ctrlp_spline = ts_int_bspline_access_ctrlp(spline);
|
|
knots_spline = ts_int_bspline_access_knots(spline);
|
|
ctrlp_result = ts_int_bspline_access_ctrlp(result);
|
|
knots_result = ts_int_bspline_access_knots(result);
|
|
num_ctrlp_result = ts_bspline_num_control_points(result);
|
|
num_knots_result = ts_bspline_num_knots(result);
|
|
|
|
/* mult + n <= deg + 1 (order) with n >= 1
|
|
* => mult <= deg
|
|
* => regular evaluation
|
|
* => N = h + 1 is valid */
|
|
N = ts_deboornet_num_insertions(net) + 1;
|
|
|
|
/* 1. Copy the relevant control points and knots from `spline'.
|
|
* 2. Copy the relevant control points and knots from `net'. */
|
|
|
|
/* 1.
|
|
*
|
|
* a) Copy left hand side control points from `spline'.
|
|
* b) Copy right hand side control points from `spline'.
|
|
* c) Copy left hand side knots from `spline'.
|
|
* d) Copy right hand side knots form `spline'. */
|
|
/*** Copy Control Points ***/
|
|
memmove(ctrlp_result, ctrlp_spline, (k-deg) * sof_ctrlp); /* a) */
|
|
from = (tsReal *) ctrlp_spline + dim*(k-deg+N);
|
|
/* n >= 0 implies to >= from */
|
|
to = ctrlp_result + dim*(k-deg+N+n);
|
|
memmove(to, from, (num_ctrlp_result-n-(k-deg+N)) * sof_ctrlp); /* b) */
|
|
/*** Copy Knots ***/
|
|
memmove(knots_result, knots_spline, (k+1) * sof_real); /* c) */
|
|
from = (tsReal *) knots_spline + k+1;
|
|
/* n >= 0 implies to >= from */
|
|
to = knots_result + k+1+n;
|
|
memmove(to, from, (num_knots_result-n-(k+1)) * sof_real); /* d) */
|
|
|
|
/* 2.
|
|
*
|
|
* a) Copy left hand side control points from `net'.
|
|
* b) Copy middle part control points from `net'.
|
|
* c) Copy right hand side control points from `net'.
|
|
* d) Copy knot from `net' (`knot'). */
|
|
from = ts_int_deboornet_access_points(net);
|
|
to = ctrlp_result + (k-deg)*dim;
|
|
stride = (int)(N*dim);
|
|
|
|
/*** Copy Control Points ***/
|
|
for (i = 0; i < n; i++) { /* a) */
|
|
memcpy(to, from, sof_ctrlp);
|
|
from += stride;
|
|
to += dim;
|
|
stride -= (int)dim;
|
|
}
|
|
memcpy(to, from, (N-n) * sof_ctrlp); /* b) */
|
|
|
|
from -= dim;
|
|
to += (N-n)*dim;
|
|
/* N = h+1 with h = deg-mult (ts_int_bspline_eval)
|
|
* => N = deg-mult+1 = order-mult.
|
|
*
|
|
* n <= order-mult
|
|
* => N-n+1 >= order-mult - order-mult + 1 = 1
|
|
* => -(int)(N-n+1) <= -1. */
|
|
stride = -(int)(N-n+1) * (int)dim;
|
|
|
|
for (i = 0; i < n; i++) { /* c) */
|
|
memcpy(to, from, sof_ctrlp);
|
|
from += stride;
|
|
stride -= (int)dim;
|
|
to += dim;
|
|
}
|
|
/*** Copy Knot ***/
|
|
to = knots_result + k+1;
|
|
for (i = 0; i < n; i++) { /* d) */
|
|
*to = knot;
|
|
to++;
|
|
}
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_insert_knot(const tsBSpline *spline,
|
|
tsReal knot,
|
|
size_t num,
|
|
tsBSpline *result,
|
|
size_t* k,
|
|
tsStatus *status)
|
|
{
|
|
tsDeBoorNet net;
|
|
tsError err;
|
|
INIT_OUT_BSPLINE(spline, result)
|
|
ts_int_deboornet_init(&net);
|
|
TS_TRY(try, err, status)
|
|
TS_CALL(try, err, ts_bspline_eval(
|
|
spline, knot, &net, status))
|
|
TS_CALL(try, err, ts_int_bspline_insert_knot(
|
|
spline, &net, num, result, status))
|
|
ts_deboornet_free(&net);
|
|
TS_CALL(try, err, ts_bspline_eval(
|
|
result, knot, &net, status))
|
|
*k = ts_deboornet_index(&net);
|
|
TS_CATCH(err)
|
|
*k = 0;
|
|
TS_FINALLY
|
|
ts_deboornet_free(&net);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_split(const tsBSpline *spline,
|
|
tsReal knot,
|
|
tsBSpline *split,
|
|
size_t* k,
|
|
tsStatus *status)
|
|
{
|
|
tsDeBoorNet net;
|
|
tsError err;
|
|
INIT_OUT_BSPLINE(spline, split)
|
|
ts_int_deboornet_init(&net);
|
|
TS_TRY(try, err, status)
|
|
TS_CALL(try, err, ts_bspline_eval(
|
|
spline, knot, &net, status))
|
|
if (ts_deboornet_multiplicity(&net)
|
|
== ts_bspline_order(spline)) {
|
|
TS_CALL(try, err, ts_bspline_copy(
|
|
spline, split, status))
|
|
*k = ts_deboornet_index(&net);
|
|
} else {
|
|
TS_CALL(try, err, ts_int_bspline_insert_knot(
|
|
spline, &net,
|
|
ts_deboornet_num_insertions(&net) + 1,
|
|
split, status))
|
|
*k = ts_deboornet_index(&net) +
|
|
ts_deboornet_num_insertions(&net) + 1;
|
|
}
|
|
TS_CATCH(err)
|
|
*k = 0;
|
|
TS_FINALLY
|
|
ts_deboornet_free(&net);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_tension(const tsBSpline *spline,
|
|
tsReal beta,
|
|
tsBSpline *out,
|
|
tsStatus *status)
|
|
{
|
|
const size_t dim = ts_bspline_dimension(spline);
|
|
const size_t N = ts_bspline_num_control_points(spline);
|
|
const tsReal* p0 = ts_int_bspline_access_ctrlp(spline);
|
|
const tsReal* pn_1 = p0 + (N-1)*dim;
|
|
|
|
tsReal s; /**< The straightening factor. */
|
|
tsReal *ctrlp; /**< Pointer to the control points of `out'. */
|
|
size_t i, d; /**< Used in for loops. */
|
|
tsReal vec; /**< Straightening vector. */
|
|
tsError err;
|
|
|
|
TS_CALL_ROE(err, ts_bspline_copy(spline, out, status))
|
|
ctrlp = ts_int_bspline_access_ctrlp(out);
|
|
if (beta < (tsReal) 0.0) beta = (tsReal) 0.0;
|
|
if (beta > (tsReal) 1.0) beta = (tsReal) 1.0;
|
|
s = 1.f - beta;
|
|
|
|
for (i = 0; i < N; i++) {
|
|
for (d = 0; d < dim; d++) {
|
|
vec = ((tsReal)i / (N-1)) * (pn_1[d] - p0[d]);
|
|
ctrlp[i*dim + d] = beta * ctrlp[i*dim + d] +
|
|
s * (p0[d] + vec);
|
|
}
|
|
}
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_to_beziers(const tsBSpline *spline,
|
|
tsBSpline *beziers,
|
|
tsStatus *status)
|
|
{
|
|
const size_t deg = ts_bspline_degree(spline);
|
|
const size_t order = ts_bspline_order(spline);
|
|
|
|
int resize; /**< Number of control points to add/remove. */
|
|
size_t k; /**< Index of the split knot value. */
|
|
tsReal u_min; /**< Minimum of the knot values. */
|
|
tsReal u_max; /**< Maximum of the knot values. */
|
|
|
|
tsBSpline tmp; /**< Temporarily stores the result. */
|
|
tsReal *knots; /**< Pointer to the knots of tmp. */
|
|
size_t num_knots; /**< Number of knots in tmp. */
|
|
|
|
tsError err;
|
|
|
|
INIT_OUT_BSPLINE(spline, beziers)
|
|
TS_CALL_ROE(err, ts_bspline_copy(spline, &tmp, status))
|
|
knots = ts_int_bspline_access_knots(&tmp);
|
|
num_knots = ts_bspline_num_knots(&tmp);
|
|
|
|
TS_TRY(try, err, status)
|
|
/* DO NOT FORGET TO UPDATE knots AND num_knots AFTER EACH
|
|
* TRANSFORMATION OF tmp! */
|
|
|
|
/* Fix first control point if necessary. */
|
|
u_min = knots[deg];
|
|
if (!ts_knots_equal(knots[0], u_min)) {
|
|
TS_CALL(try, err, ts_bspline_split(
|
|
&tmp, u_min, &tmp, &k, status))
|
|
resize = (int)(-1*deg + (deg*2 - k));
|
|
TS_CALL(try, err, ts_int_bspline_resize(
|
|
&tmp, resize, 0, &tmp, status))
|
|
knots = ts_int_bspline_access_knots(&tmp);
|
|
num_knots = ts_bspline_num_knots(&tmp);
|
|
}
|
|
|
|
/* Fix last control point if necessary. */
|
|
u_max = knots[num_knots - order];
|
|
if (!ts_knots_equal(knots[num_knots - 1], u_max)) {
|
|
TS_CALL(try, err, ts_bspline_split(
|
|
&tmp, u_max, &tmp, &k, status))
|
|
num_knots = ts_bspline_num_knots(&tmp);
|
|
resize = (int)(-1*deg + (k - (num_knots - order)));
|
|
TS_CALL(try, err, ts_int_bspline_resize(
|
|
&tmp, resize, 1, &tmp, status))
|
|
knots = ts_int_bspline_access_knots(&tmp);
|
|
num_knots = ts_bspline_num_knots(&tmp);
|
|
}
|
|
|
|
/* Split internal knots. */
|
|
k = order;
|
|
while (k < num_knots - order) {
|
|
TS_CALL(try, err, ts_bspline_split(
|
|
&tmp, knots[k], &tmp, &k, status))
|
|
knots = ts_int_bspline_access_knots(&tmp);
|
|
num_knots = ts_bspline_num_knots(&tmp);
|
|
k++;
|
|
}
|
|
|
|
if (spline == beziers)
|
|
ts_bspline_free(beziers);
|
|
ts_bspline_move(&tmp, beziers);
|
|
TS_FINALLY
|
|
ts_bspline_free(&tmp);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_elevate_degree(const tsBSpline *spline,
|
|
size_t amount,
|
|
tsReal epsilon,
|
|
tsBSpline *elevated,
|
|
tsStatus * status)
|
|
{
|
|
tsBSpline worker;
|
|
size_t dim, order;
|
|
tsReal *ctrlp, *knots;
|
|
size_t num_beziers, i, a, c, d, offset, idx;
|
|
tsReal f, f_hat, *first, *last;
|
|
tsError err;
|
|
|
|
/* Trivial case. */
|
|
if (amount == 0)
|
|
return ts_bspline_copy(spline, elevated, status);
|
|
|
|
/* An overview of this algorithm can be found at:
|
|
* https://pages.mtu.edu/~shene/COURSES/cs3621/LAB/curve/elevation.html */
|
|
INIT_OUT_BSPLINE(spline, elevated);
|
|
worker = ts_bspline_init();
|
|
TS_TRY(try, err, status)
|
|
/* Decompose `spline' into a sequence of bezier curves and make
|
|
* space for the additional control points and knots that are
|
|
* to be inserted. Results are stored in `worker'. */
|
|
TS_CALL(try, err, ts_bspline_to_beziers(
|
|
spline, &worker, status));
|
|
num_beziers = ts_bspline_num_control_points(&worker) /
|
|
ts_bspline_order(&worker);
|
|
TS_CALL(try, err, ts_int_bspline_resize(
|
|
/* Resize by the number of knots to be inserted. Note
|
|
* that this creates too many control points (due to
|
|
* increasing the degree), which are removed at the end
|
|
* of this function. */
|
|
&worker, (int) ((num_beziers+1) * amount), 1, &worker,
|
|
status));
|
|
dim = ts_bspline_dimension(&worker);
|
|
order = ts_bspline_order(&worker);
|
|
ctrlp = ts_int_bspline_access_ctrlp(&worker);
|
|
knots = ts_int_bspline_access_knots(&worker);
|
|
|
|
/* Move all but the first bezier curve to their new location in
|
|
* the control point array so that the additional control
|
|
* points can be inserted without overwriting the others. Note
|
|
* that iteration must run backwards. Otherwise, the moved
|
|
* values overwrite each other. */
|
|
for (i = num_beziers - 1; i > 0; i--) {
|
|
/* `i' can be interpreted as the number of bezier
|
|
* curves before the current bezier curve. */
|
|
|
|
/* Location of current bezier curve. */
|
|
offset = i * order * dim;
|
|
/* Each elevation inserts an additional control point
|
|
* into every bezier curve. `i * amount' is the total
|
|
* number of control points to be inserted before the
|
|
* current bezier curve. */
|
|
memmove(ctrlp + offset + (i * amount * dim),
|
|
ctrlp + offset,
|
|
dim * order * sizeof(tsReal));
|
|
}
|
|
|
|
/* Move all but the first group of knots to their new location
|
|
* in the knot vector so that the additional knots can be
|
|
* inserted without overwriting the others. Note that iteration
|
|
* must run backwards. Otherwise, the moved values overwrite
|
|
* each other. */
|
|
for (i = num_beziers; i > 0; i--) {
|
|
/* Note that the number of knot groups is one more than
|
|
* the number of bezier curves. `i' can be interpreted
|
|
* as the number of knot groups before the current
|
|
* group. */
|
|
|
|
/* Location of current knot group. */
|
|
offset = i * order;
|
|
/* Each elevation inserts an additional knot into every
|
|
* group of knots. `i * amount' is the total number of
|
|
* knots to be inserted before the current knot
|
|
* group. */
|
|
memmove(knots + offset + (i * amount),
|
|
knots + offset,
|
|
order * sizeof(tsReal));
|
|
}
|
|
|
|
/* `worker' is now fully set up.
|
|
* The following formulas are based on:
|
|
* https://pages.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/bezier-elev.html */
|
|
for (a = 0; a < amount; a++) {
|
|
/* For each bezier curve... */
|
|
for (i = 0; i < num_beziers; i++) {
|
|
/* ... 1) Insert and update control points. */
|
|
|
|
/* Location of current bezier curve. Each
|
|
* elevation (`a') inserts an additional
|
|
* control point into every bezier curve and
|
|
* increases the degree (`order') by one. The
|
|
* location is thus made up of two parts:
|
|
*
|
|
* i) `i * order', which is the location taking
|
|
* into account the increasing order but
|
|
* neglecting the control points that are to be
|
|
* inserted before the current bezier curve. It
|
|
* can be seen as some sort of base location:
|
|
* Where would the bezier curve be (with
|
|
* respect to the current value of `order') if
|
|
* no additional control points had to be
|
|
* inserted?
|
|
*
|
|
* ii) `i * (amount - a)', which is the total
|
|
* number of control points to be inserted
|
|
* before the current bezier curve
|
|
* (`i * amount') taking into account the
|
|
* increasing order (`order' and `a' are
|
|
* increased equally, thus, `a' compensates for
|
|
* the increasing value of `order'). This part
|
|
* adds the necessary offset to the base
|
|
* location (`i * order'). */
|
|
offset = (i * order + i * (amount - a)) * dim;
|
|
/* Duplicate last control point to the new end
|
|
* position (next control point). */
|
|
memmove(ctrlp + offset + ((order) * dim),
|
|
ctrlp + offset + ((order-1) * dim),
|
|
dim * sizeof(tsReal));
|
|
/* All but the outer control points must be
|
|
* recalculated (domain: [1, order - 1]). By
|
|
* traversing backwards, control points can be
|
|
* modified in-place. */
|
|
for (c = order - 1; c > 0; c--) {
|
|
/* Location of current control point
|
|
* within current bezier curve. */
|
|
idx = offset + c * dim;
|
|
f = (tsReal) c / (tsReal) (order);
|
|
f_hat = 1 - f;
|
|
for (d = 0; d < dim; d++) {
|
|
/* For the sake of space, we
|
|
* increment idx by d and
|
|
* decrement it at the end of
|
|
* this loop. */
|
|
idx += d;
|
|
ctrlp[idx] =
|
|
f * ctrlp[idx - dim] +
|
|
f_hat * ctrlp[idx];
|
|
/* Reset idx. */
|
|
idx -= d;
|
|
}
|
|
}
|
|
|
|
/* ...2) Increase the multiplicity of the
|
|
* second knot group (maximum of the domain of
|
|
* the current bezier curve) by one. Note that
|
|
* this loop misses the last knot group (the
|
|
* group of the last bezier curve) as there is
|
|
* one more knot group than bezier curves to
|
|
* process. Thus, the last group must be
|
|
* increased separately after this loop. */
|
|
|
|
/* Location of current knot group. Each
|
|
* elevation (`a') inserts an additional
|
|
* knot into the knot vector of every bezier
|
|
* curve and increases the degree (`order') by
|
|
* one. The location is thus made up of two
|
|
* parts:
|
|
*
|
|
* i) `i * order', which is the location taking
|
|
* into account the increasing order but
|
|
* neglecting the knots that are to be inserted
|
|
* before the current knot group. It can be
|
|
* seen as some sort of base location: Where
|
|
* would the knot group be (with respect to the
|
|
* current value of `order') if no additional
|
|
* knots had to be inserted?
|
|
*
|
|
* ii) `i * (amount - a)', which is the total
|
|
* number of knots to be inserted before the
|
|
* current knot group (`i * amount') taking
|
|
* into account the increasing order (`order'
|
|
* and `a' are increased equally, thus, `a'
|
|
* compensates for the increasing value of
|
|
* `order'). This part adds the necessary
|
|
* offset to the base location
|
|
* (`i * order'). */
|
|
offset = i * order + i * (amount - a);
|
|
/* Duplicate knot. */
|
|
knots[offset + order] = knots[offset];
|
|
}
|
|
|
|
/* Increase the multiplicity of the very last knot
|
|
* group (the second group of the last bezier curve)
|
|
* by one. For more details, see knot duplication in
|
|
* previous loop. */
|
|
offset = num_beziers * order +
|
|
num_beziers * (amount - a);
|
|
knots[offset + order] = knots[offset];
|
|
|
|
/* Elevated by one. */
|
|
order++;
|
|
}
|
|
|
|
/* Combine bezier curves. */
|
|
d = 0; /* Number of removed knots/control points. */
|
|
for (i = 0; i < num_beziers - 1; i++) {
|
|
/* Is the last control point of bezier curve `i' equal
|
|
* to the first control point of bezier curve `i+1'? */
|
|
last = ctrlp + (
|
|
i * order /* base location of `i' */
|
|
- d /* minus the number of removed values */
|
|
+ (order - 1) /* jump to last control point */
|
|
) * dim;
|
|
first = last + dim; /* next control point */
|
|
if (ts_distance(last, first, dim) <= epsilon) {
|
|
/* Move control points. */
|
|
memmove(last, first, (num_beziers - 1 - i) *
|
|
order * dim * sizeof(tsReal));
|
|
|
|
/* Move knots. `last' is the last knot of the
|
|
* second knot group of bezier curve `i'.
|
|
* `first' is the first knot of the first knot
|
|
* group of bezier curve `i+1'. The
|
|
* calculations are quite similar to those for
|
|
* the control points `last' and `first' (see
|
|
* above). */
|
|
last = knots + i * order - d + (2 * order - 1);
|
|
first = last + 1;
|
|
memmove(last, first, (num_beziers - 1 - i) *
|
|
order * sizeof(tsReal));
|
|
|
|
/* Removed one knot/control point. */
|
|
d++;
|
|
}
|
|
}
|
|
|
|
/* Repair internal state. */
|
|
worker.pImpl->deg = order - 1;
|
|
worker.pImpl->n_knots -= d;
|
|
worker.pImpl->n_ctrlp = ts_bspline_num_knots(&worker) - order;
|
|
memmove(ts_int_bspline_access_knots(&worker),
|
|
knots, ts_bspline_sof_knots(&worker));
|
|
worker.pImpl = realloc(worker.pImpl,
|
|
ts_int_bspline_sof_state(&worker));
|
|
if (worker.pImpl == NULL) {
|
|
TS_THROW_0(try, err, status, TS_MALLOC,
|
|
"out of memory")
|
|
}
|
|
|
|
/* Move `worker' to output parameter. */
|
|
if (spline == elevated)
|
|
ts_bspline_free(elevated);
|
|
ts_bspline_move(&worker, elevated);
|
|
TS_FINALLY
|
|
ts_bspline_free(&worker);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_align(const tsBSpline *s1,
|
|
const tsBSpline *s2,
|
|
tsReal epsilon,
|
|
tsBSpline *s1_out,
|
|
tsBSpline *s2_out,
|
|
tsStatus *status)
|
|
{
|
|
tsBSpline s1_worker, s2_worker, *smaller, *larger;
|
|
tsDeBoorNet net; /* the net of `smaller'. */
|
|
size_t i, missing, remaining;
|
|
tsReal min, max, shift, nextKnot;
|
|
tsError err;
|
|
|
|
INIT_OUT_BSPLINE(s1, s1_out)
|
|
INIT_OUT_BSPLINE(s2, s2_out)
|
|
s1_worker = ts_bspline_init();
|
|
s2_worker = ts_bspline_init();
|
|
smaller = larger = NULL;
|
|
TS_TRY(try, err, status)
|
|
/* Set up `s1_worker' and `s2_worker'. After this
|
|
* if-elseif-else-block, `s1_worker' and `s2_worker' have same
|
|
* degree. */
|
|
if (ts_bspline_degree(s1) < ts_bspline_degree(s2)) {
|
|
TS_CALL(try, err, ts_bspline_elevate_degree(s1,
|
|
ts_bspline_degree(s2) - ts_bspline_degree(s1),
|
|
epsilon, &s1_worker, status))
|
|
TS_CALL(try, err, ts_bspline_copy(
|
|
s2, &s2_worker, status))
|
|
} else if (ts_bspline_degree(s2) < ts_bspline_degree(s1)) {
|
|
TS_CALL(try, err, ts_bspline_elevate_degree(s2,
|
|
ts_bspline_degree(s1) - ts_bspline_degree(s2),
|
|
epsilon, &s2_worker, status))
|
|
TS_CALL(try, err, ts_bspline_copy(
|
|
s1, &s1_worker, status))
|
|
} else {
|
|
TS_CALL(try, err, ts_bspline_copy(
|
|
s1, &s1_worker, status))
|
|
TS_CALL(try, err, ts_bspline_copy(
|
|
s2, &s2_worker, status))
|
|
}
|
|
|
|
/* Set up `smaller', `larger', and `net'. */
|
|
if (ts_bspline_num_knots(&s1_worker) <
|
|
ts_bspline_num_knots(&s2_worker)) {
|
|
smaller = &s1_worker;
|
|
larger = &s2_worker;
|
|
} else {
|
|
smaller = &s2_worker;
|
|
larger = &s1_worker;
|
|
}
|
|
TS_CALL(try, err, ts_int_deboornet_new(
|
|
smaller, &net, status))
|
|
|
|
/* Insert knots into `smaller' until it has the same number of
|
|
* knots (and therefore the same number of control points) as
|
|
* `larger'. */
|
|
ts_bspline_domain(smaller, &min, &max);
|
|
missing = remaining = ts_bspline_num_knots(larger) -
|
|
ts_bspline_num_knots(smaller);
|
|
shift = (tsReal) 0.0;
|
|
if (missing > 0)
|
|
shift = ( (tsReal) 1.0 / missing ) * (tsReal) 0.5;
|
|
for (i = 0; remaining > 0; i++, remaining--) {
|
|
nextKnot = (max - min) * ((tsReal)i / missing) + min;
|
|
nextKnot += shift;
|
|
TS_CALL(try, err, ts_int_bspline_eval_woa(
|
|
smaller, nextKnot, &net, status))
|
|
while (!ts_deboornet_num_insertions(&net)) {
|
|
/* Linear exploration for next knot. */
|
|
nextKnot += 5 * TS_KNOT_EPSILON;
|
|
if (nextKnot > max) {
|
|
TS_THROW_0(try, err, status,
|
|
TS_NO_RESULT,
|
|
"no more knots for insertion")
|
|
}
|
|
TS_CALL(try, err, ts_int_bspline_eval_woa(
|
|
smaller, nextKnot, &net, status))
|
|
}
|
|
TS_CALL(try, err, ts_int_bspline_insert_knot(
|
|
smaller, &net, 1, smaller, status))
|
|
}
|
|
|
|
if (s1 == s1_out)
|
|
ts_bspline_free(s1_out);
|
|
if (s2 == s2_out)
|
|
ts_bspline_free(s2_out);
|
|
ts_bspline_move(&s1_worker, s1_out);
|
|
/* if `s1_out' == `s2_out', `s2_worker' must not be moved
|
|
* because otherwise the memory of `s1_worker' is leaked
|
|
* (`s2_worker' overrides `s1_worker'). */
|
|
if (s1_out != s2_out)
|
|
ts_bspline_move(&s2_worker, s2_out);
|
|
TS_FINALLY
|
|
ts_bspline_free(&s1_worker);
|
|
ts_bspline_free(&s2_worker);
|
|
ts_deboornet_free(&net);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_morph(const tsBSpline *origin,
|
|
const tsBSpline *target,
|
|
tsReal t,
|
|
tsReal epsilon,
|
|
tsBSpline *out,
|
|
tsStatus *status)
|
|
{
|
|
tsBSpline origin_al, target_al; /* aligned origin and target */
|
|
tsReal *origin_al_c, *origin_al_k; /* control points and knots */
|
|
tsReal *target_al_c, *target_al_k; /* control points and knots */
|
|
|
|
/* Properties of `out'. */
|
|
size_t deg, dim, num_ctrlp, num_knots;
|
|
tsReal *ctrlp, *knots;
|
|
tsBSpline tmp; /* temporary buffer if `out' must be resized */
|
|
|
|
tsReal t_hat;
|
|
size_t i, offset, d;
|
|
tsError err;
|
|
|
|
origin_al = ts_bspline_init();
|
|
target_al = ts_bspline_init();
|
|
TS_TRY(try, err, status)
|
|
/* Clamp `t' to domain [0, 1] and set up `t_hat'. */
|
|
if (t < (tsReal) 0.0) t = (tsReal) 0.0;
|
|
if (t > (tsReal) 1.0) t = (tsReal) 1.0;
|
|
t_hat = (tsReal) 1.0 - t;
|
|
|
|
/* Set up `origin_al' and `target_al'. */
|
|
/* Degree must be elevated... */
|
|
if (ts_bspline_degree(origin) != ts_bspline_degree(target) ||
|
|
/* .. or knots (and thus control points) must be inserted. */
|
|
ts_bspline_num_knots(origin) != ts_bspline_num_knots(target)) {
|
|
TS_CALL(try, err, ts_bspline_align(
|
|
origin, target, epsilon, &origin_al, &target_al,
|
|
status));
|
|
} else {
|
|
/* Flat copy. */
|
|
origin_al = *origin;
|
|
target_al = *target;
|
|
}
|
|
origin_al_c = ts_int_bspline_access_ctrlp(&origin_al);
|
|
origin_al_k = ts_int_bspline_access_knots(&origin_al);
|
|
target_al_c = ts_int_bspline_access_ctrlp(&target_al);
|
|
target_al_k = ts_int_bspline_access_knots(&target_al);
|
|
|
|
/* Set up `out'. */
|
|
deg = ts_bspline_degree(&origin_al);
|
|
num_ctrlp = ts_bspline_num_control_points(&origin_al);
|
|
dim = ts_bspline_dimension(&origin_al);
|
|
if (ts_bspline_dimension(&target_al) < dim)
|
|
dim = ts_bspline_dimension(&target_al);
|
|
if (out->pImpl == NULL) {
|
|
TS_CALL(try, err, ts_bspline_new(num_ctrlp, dim, deg,
|
|
TS_OPENED /* doesn't matter */, out, status))
|
|
} else if (ts_bspline_degree(out) != deg ||
|
|
ts_bspline_num_control_points(out) != num_ctrlp ||
|
|
ts_bspline_dimension(out) != dim) {
|
|
TS_CALL(try, err, ts_bspline_new(num_ctrlp, dim, deg,
|
|
TS_OPENED /* doesn't matter */, &tmp, status))
|
|
ts_bspline_free(out);
|
|
ts_bspline_move(&tmp, out);
|
|
}
|
|
num_knots = ts_bspline_num_knots(out);
|
|
ctrlp = ts_int_bspline_access_ctrlp(out);
|
|
knots = ts_int_bspline_access_knots(out);
|
|
|
|
/* Interpolate control points. */
|
|
for (i = 0; i < num_ctrlp; i++) {
|
|
for (d = 0; d < dim; d++) {
|
|
offset = i * dim + d;
|
|
ctrlp[offset] = t * target_al_c[offset] +
|
|
t_hat * origin_al_c[offset];
|
|
}
|
|
}
|
|
|
|
/* Interpolate knots. */
|
|
for (i = 0; i < num_knots; i++) {
|
|
knots[i] = t * target_al_k[i] +
|
|
t_hat * origin_al_k[i];
|
|
}
|
|
TS_FINALLY
|
|
if (origin->pImpl != origin_al.pImpl)
|
|
ts_bspline_free(&origin_al);
|
|
if (target->pImpl != target_al.pImpl)
|
|
ts_bspline_free(&target_al);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
/*! @} */
|
|
|
|
|
|
|
|
/*! @name Serialization and Persistence
|
|
*
|
|
* @{
|
|
*/
|
|
tsError
|
|
ts_int_bspline_to_json(const tsBSpline *spline,
|
|
JSON_Value **value,
|
|
tsStatus *status)
|
|
{
|
|
const size_t deg = ts_bspline_degree(spline);
|
|
const size_t dim = ts_bspline_dimension(spline);
|
|
const size_t len_ctrlp = ts_bspline_len_control_points(spline);
|
|
const size_t len_knots = ts_bspline_num_knots(spline);
|
|
const tsReal *ctrlp = ts_int_bspline_access_ctrlp(spline);
|
|
const tsReal *knots = ts_int_bspline_access_knots(spline);
|
|
|
|
size_t i; /**< Used in loops */
|
|
tsError err;
|
|
|
|
JSON_Value *ctrlp_value;
|
|
JSON_Value *knots_value;
|
|
JSON_Object *spline_object;
|
|
JSON_Array *ctrlp_array;
|
|
JSON_Array *knots_array;
|
|
|
|
*value = ctrlp_value = knots_value = NULL;
|
|
TS_TRY(values, err, status)
|
|
/* Init memory. */
|
|
*value = json_value_init_object();
|
|
if (!*value) {
|
|
TS_THROW_0(values, err, status, TS_MALLOC,
|
|
"out of memory")
|
|
}
|
|
ctrlp_value = json_value_init_array();
|
|
if (!ctrlp_value) {
|
|
TS_THROW_0(values, err, status, TS_MALLOC,
|
|
"out of memory")
|
|
}
|
|
knots_value = json_value_init_array();
|
|
if (!knots_value) {
|
|
TS_THROW_0(values, err, status, TS_MALLOC,
|
|
"out of memory")
|
|
}
|
|
|
|
/* Although the following functions cannot fail, that is, they
|
|
* won't return NULL or JSONFailure, we nevertheless handle
|
|
* unexpected return values. */
|
|
|
|
/* Init output. */
|
|
spline_object = json_value_get_object(*value);
|
|
if (!spline_object) {
|
|
TS_THROW_0(values, err, status, TS_MALLOC,
|
|
"out of memory")
|
|
}
|
|
|
|
/* Set degree and dimension. */
|
|
if (JSONSuccess != json_object_set_number(spline_object,
|
|
"degree",
|
|
(double) deg)) {
|
|
TS_THROW_0(values, err, status, TS_MALLOC,
|
|
"out of memory")
|
|
}
|
|
if (JSONSuccess != json_object_set_number(spline_object,
|
|
"dimension",
|
|
(double) dim)) {
|
|
TS_THROW_0(values, err, status, TS_MALLOC,
|
|
"out of memory")
|
|
}
|
|
|
|
/* Set control points. */
|
|
if (JSONSuccess != json_object_set_value(spline_object,
|
|
"control_points",
|
|
ctrlp_value)) {
|
|
TS_THROW_0(values, err, status, TS_MALLOC,
|
|
"out of memory")
|
|
}
|
|
ctrlp_array = json_array(ctrlp_value);
|
|
if (!ctrlp_array) {
|
|
TS_THROW_0(values, err, status, TS_MALLOC,
|
|
"out of memory")
|
|
}
|
|
for (i = 0; i < len_ctrlp; i++) {
|
|
if (JSONSuccess != json_array_append_number(
|
|
ctrlp_array, (double) ctrlp[i])) {
|
|
TS_THROW_0(values, err, status, TS_MALLOC,
|
|
"out of memory")
|
|
}
|
|
}
|
|
|
|
/* Set knots. */
|
|
if (JSONSuccess != json_object_set_value(spline_object,
|
|
"knots",
|
|
knots_value)) {
|
|
TS_THROW_0(values, err, status, TS_MALLOC,
|
|
"out of memory")
|
|
}
|
|
knots_array = json_array(knots_value);
|
|
if (!knots_array) {
|
|
TS_THROW_0(values, err, status, TS_MALLOC,
|
|
"out of memory")
|
|
}
|
|
for (i = 0; i < len_knots; i++) {
|
|
if (JSONSuccess != json_array_append_number(
|
|
knots_array, (double) knots[i])) {
|
|
TS_THROW_0(values, err, status, TS_MALLOC,
|
|
"out of memory")
|
|
}
|
|
}
|
|
TS_CATCH(err)
|
|
if (*value)
|
|
json_value_free(*value);
|
|
if (ctrlp_value && !json_value_get_parent(ctrlp_value))
|
|
json_value_free(ctrlp_value);
|
|
if (knots_value && !json_value_get_parent(knots_value))
|
|
json_value_free(knots_value);
|
|
*value = NULL;
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
tsError
|
|
ts_int_bspline_parse_json(const JSON_Value *spline_value,
|
|
tsBSpline *spline,
|
|
tsStatus *status)
|
|
{
|
|
size_t deg, dim, len_ctrlp, num_knots;
|
|
tsReal *ctrlp, *knots;
|
|
|
|
JSON_Object *spline_object;
|
|
JSON_Value *deg_value;
|
|
JSON_Value *dim_value;
|
|
JSON_Value *ctrlp_value;
|
|
JSON_Array *ctrlp_array;
|
|
JSON_Value *knots_value;
|
|
JSON_Array *knots_array;
|
|
JSON_Value *real_value;
|
|
size_t i;
|
|
tsError err;
|
|
|
|
ts_int_bspline_init(spline);
|
|
|
|
/* Read spline object. */
|
|
if (json_value_get_type(spline_value) != JSONObject)
|
|
TS_RETURN_0(status, TS_PARSE_ERROR, "invalid json input")
|
|
spline_object = json_value_get_object(spline_value);
|
|
if (!spline_object)
|
|
TS_RETURN_0(status, TS_PARSE_ERROR, "invalid json input")
|
|
|
|
/* Read degree. */
|
|
deg_value = json_object_get_value(spline_object, "degree");
|
|
if (json_value_get_type(deg_value) != JSONNumber)
|
|
TS_RETURN_0(status, TS_PARSE_ERROR, "degree is not a number")
|
|
if (json_value_get_number(deg_value) < -0.01f) {
|
|
TS_RETURN_1(status, TS_PARSE_ERROR,
|
|
"degree (%f) < 0",
|
|
json_value_get_number(deg_value))
|
|
}
|
|
deg = (size_t) json_value_get_number(deg_value);
|
|
|
|
/* Read dimension. */
|
|
dim_value = json_object_get_value(spline_object, "dimension");
|
|
if (json_value_get_type(dim_value) != JSONNumber) {
|
|
TS_RETURN_0(status, TS_PARSE_ERROR,
|
|
"dimension is not a number")
|
|
}
|
|
if (json_value_get_number(dim_value) < 0.99f) {
|
|
TS_RETURN_1(status, TS_PARSE_ERROR,
|
|
"dimension (%f) < 1",
|
|
json_value_get_number(deg_value))
|
|
}
|
|
dim = (size_t) json_value_get_number(dim_value);
|
|
|
|
/* Read length of control point vector. */
|
|
ctrlp_value = json_object_get_value(spline_object, "control_points");
|
|
if (json_value_get_type(ctrlp_value) != JSONArray) {
|
|
TS_RETURN_0(status, TS_PARSE_ERROR,
|
|
"control_points is not an array")
|
|
}
|
|
ctrlp_array = json_value_get_array(ctrlp_value);
|
|
len_ctrlp = json_array_get_count(ctrlp_array);
|
|
if (len_ctrlp % dim != 0) {
|
|
TS_RETURN_2(status, TS_PARSE_ERROR,
|
|
"len(control_points) (%lu) %% dimension (%lu) != 0",
|
|
(unsigned long) len_ctrlp, (unsigned long) dim)
|
|
}
|
|
|
|
/* Read number of knots. */
|
|
knots_value = json_object_get_value(spline_object, "knots");
|
|
if (json_value_get_type(knots_value) != JSONArray) {
|
|
TS_RETURN_0(status, TS_PARSE_ERROR,
|
|
"knots is not an array")
|
|
}
|
|
knots_array = json_value_get_array(knots_value);
|
|
num_knots = json_array_get_count(knots_array);
|
|
|
|
/* Create spline. */
|
|
TS_TRY(try, err, status)
|
|
TS_CALL(try, err, ts_bspline_new(
|
|
len_ctrlp/dim, dim, deg,
|
|
TS_CLAMPED, spline, status))
|
|
if (num_knots != ts_bspline_num_knots(spline))
|
|
TS_THROW_2(try, err, status, TS_NUM_KNOTS,
|
|
"unexpected num(knots): (%lu) != (%lu)",
|
|
(unsigned long) num_knots,
|
|
(unsigned long) ts_bspline_num_knots(spline))
|
|
|
|
/* Set control points. */
|
|
ctrlp = ts_int_bspline_access_ctrlp(spline);
|
|
for (i = 0; i < len_ctrlp; i++) {
|
|
real_value = json_array_get_value(ctrlp_array, i);
|
|
if (json_value_get_type(real_value) != JSONNumber)
|
|
TS_THROW_1(try, err, status, TS_PARSE_ERROR,
|
|
"control_points: value at index %lu is not a number",
|
|
(unsigned long) i)
|
|
ctrlp[i] = (tsReal) json_value_get_number(real_value);
|
|
}
|
|
TS_CALL(try, err, ts_bspline_set_control_points(
|
|
spline, ctrlp, status))
|
|
|
|
/* Set knots. */
|
|
knots = ts_int_bspline_access_knots(spline);
|
|
for (i = 0; i < num_knots; i++) {
|
|
real_value = json_array_get_value(knots_array, i);
|
|
if (json_value_get_type(real_value) != JSONNumber)
|
|
TS_THROW_1(try, err, status, TS_PARSE_ERROR,
|
|
"knots: value at index %lu is not a number",
|
|
(unsigned long) i)
|
|
knots[i] = (tsReal) json_value_get_number(real_value);
|
|
}
|
|
TS_CALL(try, err, ts_bspline_set_knots(
|
|
spline, knots, status))
|
|
TS_CATCH(err)
|
|
ts_bspline_free(spline);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_to_json(const tsBSpline *spline,
|
|
char **json,
|
|
tsStatus *status)
|
|
{
|
|
tsError err;
|
|
JSON_Value *value = NULL;
|
|
*json = NULL;
|
|
TS_CALL_ROE(err, ts_int_bspline_to_json(spline, &value, status))
|
|
*json = json_serialize_to_string_pretty(value);
|
|
json_value_free(value);
|
|
if (!*json)
|
|
TS_RETURN_0(status, TS_MALLOC, "out of memory")
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_parse_json(const char *json,
|
|
tsBSpline *spline,
|
|
tsStatus *status)
|
|
{
|
|
tsError err;
|
|
JSON_Value *value = NULL;
|
|
ts_int_bspline_init(spline);
|
|
TS_TRY(try, err, status)
|
|
value = json_parse_string(json);
|
|
if (!value) {
|
|
TS_RETURN_0(status, TS_PARSE_ERROR,
|
|
"invalid json input")
|
|
}
|
|
TS_CALL(try, err, ts_int_bspline_parse_json(
|
|
value, spline, status))
|
|
TS_FINALLY
|
|
if (value)
|
|
json_value_free(value);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_save(const tsBSpline *spline,
|
|
const char *path,
|
|
tsStatus *status)
|
|
{
|
|
tsError err;
|
|
JSON_Status json_status;
|
|
JSON_Value *value = NULL;
|
|
TS_CALL_ROE(err, ts_int_bspline_to_json(spline, &value, status))
|
|
json_status = json_serialize_to_file_pretty(value, path);
|
|
json_value_free(value);
|
|
if (json_status != JSONSuccess)
|
|
TS_RETURN_0(status, TS_IO_ERROR, "unexpected io error")
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
tsError
|
|
ts_bspline_load(const char *path,
|
|
tsBSpline *spline,
|
|
tsStatus *status)
|
|
{
|
|
tsError err;
|
|
FILE *file = NULL;
|
|
JSON_Value *value = NULL;
|
|
ts_int_bspline_init(spline);
|
|
TS_TRY(try, err, status)
|
|
file = fopen(path, "r");
|
|
if (!file) {
|
|
TS_THROW_0(try, err, status, TS_IO_ERROR,
|
|
"unable to open file")
|
|
}
|
|
value = json_parse_file(path);
|
|
if (!value) {
|
|
TS_RETURN_0(status, TS_PARSE_ERROR,
|
|
"invalid json input")
|
|
}
|
|
TS_CALL(try, err, ts_int_bspline_parse_json(
|
|
value, spline, status))
|
|
TS_FINALLY
|
|
if (file)
|
|
fclose(file);
|
|
if (value)
|
|
json_value_free(value);
|
|
TS_CATCH(err)
|
|
ts_bspline_free(spline);
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
/*! @} */
|
|
|
|
|
|
|
|
/*! @name Vector Math
|
|
* @{
|
|
*/
|
|
void
|
|
ts_vec2_init(tsReal *out,
|
|
tsReal x,
|
|
tsReal y)
|
|
{
|
|
out[0] = x;
|
|
out[1] = y;
|
|
}
|
|
|
|
void
|
|
ts_vec3_init(tsReal *out,
|
|
tsReal x,
|
|
tsReal y,
|
|
tsReal z)
|
|
{
|
|
out[0] = x;
|
|
out[1] = y;
|
|
out[2] = z;
|
|
}
|
|
|
|
void
|
|
ts_vec4_init(tsReal *out,
|
|
tsReal x,
|
|
tsReal y,
|
|
tsReal z,
|
|
tsReal w)
|
|
{
|
|
out[0] = x;
|
|
out[1] = y;
|
|
out[2] = z;
|
|
out[3] = w;
|
|
}
|
|
|
|
void
|
|
ts_vec2_set(tsReal *out,
|
|
const tsReal *x,
|
|
size_t dim)
|
|
{
|
|
const size_t n = dim > 2 ? 2 : dim;
|
|
memmove(out, x, n * sizeof(tsReal));
|
|
if (dim < 2)
|
|
ts_arr_fill(out + dim, 2 - dim, (tsReal) 0.0);
|
|
}
|
|
|
|
void
|
|
ts_vec3_set(tsReal *out,
|
|
const tsReal *x,
|
|
size_t dim)
|
|
{
|
|
const size_t n = dim > 3 ? 3 : dim;
|
|
memmove(out, x, n * sizeof(tsReal));
|
|
if (dim < 3)
|
|
ts_arr_fill(out + dim, 3 - dim, (tsReal) 0.0);
|
|
}
|
|
|
|
void
|
|
ts_vec4_set(tsReal *out,
|
|
const tsReal *x,
|
|
size_t dim)
|
|
{
|
|
const size_t n = dim > 4 ? 4 : dim;
|
|
memmove(out, x, n * sizeof(tsReal));
|
|
if (dim < 4)
|
|
ts_arr_fill(out + dim, 4 - dim, (tsReal) 0.0);
|
|
}
|
|
|
|
void
|
|
ts_vec_add(const tsReal *x,
|
|
const tsReal *y,
|
|
size_t dim,
|
|
tsReal *out)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < dim; i++)
|
|
out[i] = x[i] + y[i];
|
|
}
|
|
|
|
void
|
|
ts_vec_sub(const tsReal *x,
|
|
const tsReal *y,
|
|
size_t dim,
|
|
tsReal *out)
|
|
{
|
|
size_t i;
|
|
if (x == y) {
|
|
/* More stable version. */
|
|
ts_arr_fill(out, dim, (tsReal) 0.0);
|
|
return;
|
|
}
|
|
for (i = 0; i < dim; i++)
|
|
out[i] = x[i] - y[i];
|
|
}
|
|
|
|
tsReal
|
|
ts_vec_dot(const tsReal *x,
|
|
const tsReal *y,
|
|
size_t dim)
|
|
{
|
|
size_t i;
|
|
tsReal dot = 0;
|
|
for (i = 0; i < dim; i++)
|
|
dot += x[i] * y[i];
|
|
return dot;
|
|
}
|
|
|
|
tsReal
|
|
ts_vec_angle(const tsReal *x,
|
|
const tsReal *y,
|
|
tsReal *buf,
|
|
size_t dim)
|
|
{
|
|
const tsReal *x_norm, *y_norm;
|
|
if (buf) {
|
|
ts_vec_norm(x, dim, buf);
|
|
ts_vec_norm(y, dim, buf + dim);
|
|
x_norm = buf;
|
|
y_norm = buf + dim;
|
|
} else {
|
|
x_norm = x;
|
|
y_norm = y;
|
|
}
|
|
return (tsReal) (
|
|
/* Use doubles as long as possible. */
|
|
acos(ts_vec_dot(x_norm,
|
|
y_norm,
|
|
dim))
|
|
* (180.0 / TS_PI) /* radiant to degree */
|
|
);
|
|
}
|
|
|
|
void
|
|
ts_vec3_cross(const tsReal *x,
|
|
const tsReal *y,
|
|
tsReal *out)
|
|
{
|
|
tsReal a, b, c;
|
|
a = x[1] * y[2] - x[2] * y[1];
|
|
b = x[2] * y[0] - x[0] * y[2];
|
|
c = x[0] * y[1] - x[1] * y[0];
|
|
out[0] = a;
|
|
out[1] = b;
|
|
out[2] = c;
|
|
}
|
|
|
|
void
|
|
ts_vec_norm(const tsReal *x,
|
|
size_t dim,
|
|
tsReal *out)
|
|
{
|
|
size_t i;
|
|
const tsReal m = ts_vec_mag(x, dim);
|
|
if (m < TS_LENGTH_ZERO) {
|
|
ts_arr_fill(out, dim, (tsReal) 0.0);
|
|
return;
|
|
}
|
|
for (i = 0; i < dim; i++)
|
|
out[i] = x[i] / m;
|
|
}
|
|
|
|
tsReal
|
|
ts_vec_mag(const tsReal *x,
|
|
size_t dim)
|
|
{
|
|
size_t i;
|
|
tsReal sum = 0;
|
|
for (i = 0; i < dim; i++)
|
|
sum += (x[i] * x[i]);
|
|
return (tsReal) sqrt(sum);
|
|
}
|
|
|
|
void
|
|
ts_vec_mul(const tsReal *x,
|
|
size_t dim,
|
|
tsReal val,
|
|
tsReal *out)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < dim; i++)
|
|
out[i] = x[i] * val;
|
|
}
|
|
/*! @} */
|
|
|
|
|
|
|
|
/*! @name Chord Length Method
|
|
*
|
|
* @{
|
|
*/
|
|
tsError
|
|
ts_chord_lengths_length_to_knot(const tsReal *knots,
|
|
const tsReal *lengths,
|
|
size_t num,
|
|
tsReal len,
|
|
tsReal *knot,
|
|
tsStatus *status)
|
|
{
|
|
tsReal numer, denom, r, r_hat;
|
|
size_t idx, low, high;
|
|
|
|
/* Handle spacial cases. */
|
|
if (num == 0) { /* well... */
|
|
TS_RETURN_0(status, TS_NO_RESULT, "empty chord lengths")
|
|
}
|
|
if (num == 1) { /* no computation needed */
|
|
*knot = knots[0];
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
if (lengths[num - 1] < TS_LENGTH_ZERO) { /* spline is too short */
|
|
*knot = knots[0];
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
if (len <= lengths[0]) { /* clamp `len' to lower bound */
|
|
*knot = knots[0];
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
if (len >= lengths[num - 1]) { /* clamp `len' to upper bound */
|
|
*knot = knots[num - 1];
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
/* From now on: i) `len' is less than the last chord length in
|
|
`lengths' and ii) `lengths' contains at least two values. Hence, the
|
|
index (`idx') determined by the following binary search cannot be
|
|
the last index in `knots' and `lengths', respectively (i.e., `idx <=
|
|
num - 2`). It is therefore safe to access `knots' and `lengths' at
|
|
index `idx + 1`. */
|
|
|
|
/* Binary search. Similar to how locating a knot within a knot vector
|
|
is implemented in ::ts_int_bspline_find_knot. */
|
|
low = 0;
|
|
high = num - 1;
|
|
idx = (low+high) / 2;
|
|
while (len < lengths[idx] || len >= lengths[(idx) + 1]) {
|
|
if (len < lengths[idx]) high = idx;
|
|
else low = idx;
|
|
idx = (low+high) / 2;
|
|
}
|
|
|
|
/* Determine `knot' by linear interpolation. */
|
|
denom = lengths[(idx) + 1] - lengths[idx];
|
|
if (denom < TS_LENGTH_ZERO) { /* segment is too short */
|
|
*knot = knots[idx];
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
numer = len - lengths[idx];
|
|
r = numer / denom; /* denom >= TS_LENGTH_ZERO */
|
|
r_hat = (tsReal) 1.0 - r;
|
|
*knot = r * knots[(idx) + 1] + r_hat * knots[idx];
|
|
TS_RETURN_SUCCESS(status)
|
|
}
|
|
|
|
tsError
|
|
ts_chord_lengths_t_to_knot(const tsReal *knots,
|
|
const tsReal *lengths,
|
|
size_t num,
|
|
tsReal t,
|
|
tsReal *knot,
|
|
tsStatus *status)
|
|
{
|
|
/* Delegate error handling. If `num' is `0`,
|
|
`ts_chord_lengths_length_to_knot' doesn't read `len' at all. */
|
|
tsReal len = num == 0 ? 0 : t * lengths[num - 1];
|
|
return ts_chord_lengths_length_to_knot(knots,
|
|
lengths,
|
|
num,
|
|
len,
|
|
knot,
|
|
status);
|
|
}
|
|
|
|
tsError
|
|
ts_chord_lengths_equidistant_knot_seq(const tsReal *knots,
|
|
const tsReal *lengths,
|
|
size_t num,
|
|
size_t num_knot_seq,
|
|
tsReal *knot_seq,
|
|
tsStatus *status)
|
|
{
|
|
tsError err;
|
|
size_t i;
|
|
tsReal t, knot;
|
|
if (num_knot_seq == 0) TS_RETURN_SUCCESS(status)
|
|
TS_TRY(try, err, status)
|
|
for (i = 0; i < num_knot_seq; i++) {
|
|
t = (tsReal) i / (num_knot_seq - 1);
|
|
TS_CALL(try, err, ts_chord_lengths_t_to_knot(
|
|
knots, lengths, num, t, &knot, status))
|
|
knot_seq[i] = knot;
|
|
}
|
|
/* Set `knot_seq[0]` after `knot_seq[num_knot_seq - 1]` to
|
|
ensure that `knot_seq[0] = min` if `num_knot_seq` is
|
|
`1'. Note that `num_knot_seq` and `num` can't be `0'. */
|
|
knot_seq[num_knot_seq - 1] = knots[num - 1];
|
|
knot_seq[0] = knots[0];
|
|
TS_END_TRY_RETURN(err)
|
|
}
|
|
/*! @} */
|
|
|
|
|
|
|
|
/*! @name Utility Functions
|
|
*
|
|
* @{
|
|
*/
|
|
int ts_knots_equal(tsReal x,
|
|
tsReal y)
|
|
{
|
|
return fabs(x-y) < TS_KNOT_EPSILON ? 1 : 0;
|
|
}
|
|
|
|
void ts_arr_fill(tsReal *arr,
|
|
size_t num,
|
|
tsReal val)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < num; i++)
|
|
arr[i] = val;
|
|
}
|
|
|
|
tsReal ts_distance(const tsReal *x,
|
|
const tsReal *y,
|
|
size_t dim)
|
|
{
|
|
size_t i;
|
|
tsReal sum = 0;
|
|
for (i = 0; i < dim; i++)
|
|
sum += (x[i] - y[i]) * (x[i] - y[i]);
|
|
return (tsReal) sqrt(sum);
|
|
}
|
|
/*! @} */
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma warning(pop)
|
|
#endif
|