text: /** @file */
  
  #ifndef TINYSPLINE_H
  #define TINYSPLINE_H
  
  #include <stddef.h>
  
  
  
  /*! @name Deprecation
   *
   * The macro \c TS_DEPRECATED can be used to mark functions as
   * deprecated.
   *
   * @{
   */
  #if defined(__GNUC__) || defined(__clang__)
  #define TS_DEPRECATED __attribute__((deprecated))
  #elif defined(_MSC_VER)
  #define TS_DEPRECATED __declspec(deprecated)
  #elif defined(SWIG)
  #define TS_DEPRECATED
  #else
  #warning "WARNING: TS_DEPRECATED is not supported by compiler"
  #define TS_DEPRECATED
  #endif
  /*! @} */
  
  
  
  /*! @name Library Export/Import
   *
   * If TinySpline is built for Windows, the macros \c TINYSPLINE_SHARED_EXPORT
   * and \c TINYSPLINE_SHARED_IMPORT define the Microsoft specific directives \c
   * __declspec(dllexport) and \c __declspec(dllimport), respectively. More
   * information on these directives can be found at:
   *
   *     https://docs.microsoft.com/en-us/cpp/cpp/dllexport-dllimport
   *
   * If TinySpline is built to the ELF (most Unix like environments) or Mach (OS
   * X) object format, \c TINYSPLINE_SHARED_EXPORT defines the directive
   * <tt>__attribute__ ((visibility ("default")))</tt> which, in combination with
   * \c -fvisibility=hidden, behaves similar to \c __declspec(dllexport). \c
   * TINYSPLINE_SHARED_IMPORT is set empty (i.e., it defines nothing).
   *
   * If none of the above applies, \c TINYSPLINE_SHARED_EXPORT and \c
   * TINYSPLINE_SHARED_IMPORT are set empty (i.e., they define nothing).
   *
   * Depending on whether TinySpline is compiled <em>as shared library</em> or
   * linked against <em>as shared library</em>, \c TINYSPLINE_API points to \c
   * TINYSPLINE_SHARED_EXPORT (compiled) or \c TINYSPLINE_SHARED_IMPORT (linked
   * against). All elements of TinySpline that needs to be exported/imported are
   * annotated with \c TINYSPLINE_API. This eliminates the need for a
   * module-definition (.def) file. If TinySpline is compiled or linked against
   * <em>as static library</em>, \c TINYSPLINE_API is set empty (i.e., it defines
   * nothing).
   *
   * If you consume TinySpline as shared library built for Windows, all you need
   * is to define \c TINYSPLINE_SHARED. This will automatically import all
   * required symbols. When compiling TinySpline, the build system should set all
   * necessary defines.
   *
   * @{
   */
  #if defined(_WIN32) || defined(__CYGWIN__)
  #define TINYSPLINE_SHARED_EXPORT __declspec(dllexport)
  #define TINYSPLINE_SHARED_IMPORT __declspec(dllimport)
  #elif defined(__ELF__) || defined(__MACH__)
  #define TINYSPLINE_SHARED_EXPORT __attribute__ ((visibility ("default")))
  #define TINYSPLINE_SHARED_IMPORT
  #else
  #define TINYSPLINE_SHARED_EXPORT
  #define TINYSPLINE_SHARED_IMPORT
  #endif
  
  #ifdef TINYSPLINE_SHARED
  #ifdef TINYSPLINE_EXPORT
  #define TINYSPLINE_API TINYSPLINE_SHARED_EXPORT
  #else
  #define TINYSPLINE_API TINYSPLINE_SHARED_IMPORT
  #endif
  #else
  #define TINYSPLINE_API
  #endif
  
  #ifdef	__cplusplus
  extern "C" {
  #endif
  /*! @} */
  
  
  
  /*! @name Predefined Constants
   *
   * The following constants have been adjusted to maintain internal consistency
   * and should only be changed with great caution! The values chosen should be
   * suitable for most environments and can be used with float (single) and
   * double precision (see ::tsReal). If changes are necessary, please read the
   * documentation of the constants in advance.
   *
   * @{
   */
  /**
   * The mathematical constant pi.
   */
  #define TS_PI 3.14159265358979323846
  
  /**
   * The maximum number of knots a spline can have. This constant is strongly
   * related to ::TS_KNOT_EPSILON in that the larger ::TS_MAX_NUM_KNOTS is, the
   * less precise ::TS_KNOT_EPSILON has to be (i.e., knots with greater distance
   * are considered equal). Likewise, the more precise ::TS_KNOT_EPSILON is
   * (i.e., knots with smaller distance are considered equal), the less
   * ::TS_MAX_NUM_KNOTS has to be. By default, the relation between
   * ::TS_MAX_NUM_KNOTS and ::TS_KNOT_EPSILON is as follows:
   *
   *     TS_MAX_NUM_KNOTS = 1 / TS_KNOTS_EPSILON
   */
  #define TS_MAX_NUM_KNOTS 10000
  
  /**
   * The minimum of the domain of newly created splines. Must be less than
   * ::TS_DOMAIN_DEFAULT_MAX. This constant is used only when creating new
   * splines. After creation, the domain of a spline can be adjusted as needed.
   */
  #define TS_DOMAIN_DEFAULT_MIN 0.0f
  
  /**
   * The maximum of the domain of newly created splines. Must be greater than
   * ::TS_DOMAIN_DEFAULT_MIN. This constant is used only when creating new
   * splines. After creation, the domain of a spline can be adjusted as needed.
   */
  #define TS_DOMAIN_DEFAULT_MAX 1.0f
  
  /**
   * If the distance between two knots falls below this threshold, they are
   * considered equal. Must be positive ( > 0 ). This constant is strongly
   * related to ::TS_MAX_NUM_KNOTS in that the more precise ::TS_KNOT_EPSILON is
   * (i.e., knots with smaller distance are considered equal), the less
   * ::TS_MAX_NUM_KNOTS has to be. Likewise, the larger ::TS_MAX_NUM_KNOTS is,
   * the less precise ::TS_KNOT_EPSILON has to be (i.e., knots with greater
   * distance are considered equal). By default, the relation between
   * ::TS_KNOT_EPSILON and ::TS_MAX_NUM_KNOTS is as follows:
   *
   *     TS_KNOT_EPSILON = 1 / TS_MAX_NUM_KNOTS
   *
   * It is recommended that ::TS_KNOT_EPSILON is aligned to the span of
   * ::TS_DOMAIN_DEFAULT_MIN and ::TS_DOMAIN_DEFAULT_MAX. That is, adjacent
   * floating point values in the domain [::TS_DOMAIN_DEFAULT_MIN,
   * ::TS_DOMAIN_DEFAULT_MAX] should not be equal according to
   * ::TS_KNOT_EPSILON. This is in particular recommended when ::TS_KNOT_EPSILON
   * and ::TS_MAX_NUM_KNOTS are related to each other as described above.
   */
  #define TS_KNOT_EPSILON 1e-4f
  
  /**
   * If the distance between two (control) points is less than or equal to this
   * threshold, they are considered equal. This constant is not used directly by
   * the C interface. Rather, it serves as a viable default value for functions
   * requiring an epsilon environment to decide whether two (control) points are
   * equal or not. The C++ interface, for example, uses this as default value for
   * optional parameters.
   */
  #ifdef TINYSPLINE_FLOAT_PRECISION
  #define TS_POINT_EPSILON 1e-3f
  #else
  #define TS_POINT_EPSILON 1e-5f
  #endif
  
  /**
   * If the length of an element (e.g., a vector) is less than this threshold,
   * the length is considered \c 0. Must be positive ( > 0 ).
   */
  #ifdef TINYSPLINE_FLOAT_PRECISION
  #define TS_LENGTH_ZERO 1e-3f
  #else
  #define TS_LENGTH_ZERO 1e-4f
  #endif
  /*! @} */
  
  
  
  /*! @name API Configuration
   *
   * In the following section, different aspects of TinySpline's API can be
   * configured (compile-time). It is recommended to configure the API by
   * supplying the corresponding preprocessor definition(s). That said, there is
   * nothing wrong with editing the source code directly.
   *
   * @{
   */
  /**
   * TinySpline uses its own typedef for floating point numbers. Supported are
   * floats (single precision) and doubles (double precision). By default,
   * doubles are used. Note that this typedef affects the entire API (i.e., types
   * are not mixed; all structs and functions rely only on tsReal). Float
   * precision is primarily used in combination with GLUT because GLUT's API
   * doesn't support doubles:
   *
   *     https://www.glprogramming.com/red/chapter12.html
   *
   * Generally, double precision is the right choice. Floats are mainly supported
   * for legacy reasons. Yet, floats are not considered deprecated! If necessary,
   * tsReal can also be typedefed to any other floating point representation. In
   * this case, make sure to adjust TS_MAX_NUM_KNOTS and TS_KNOT_EPSILON
   * (cf. Section "Predefined Constants").
   */
  #ifdef TINYSPLINE_FLOAT_PRECISION
  typedef float tsReal;
  #else
  typedef double tsReal;
  #endif
  /*! @} */
  
  
  
  /*! @name Error Handling
   *
   * There are three types of error handling in TinySpline.
   *
   * 1. Return value: Functions that can fail return a special error code
   * (::tsError). If the error code is not \c 0 (::TS_SUCCESS), an error occurred
   * during execution. For example:
   *
   *     if ( ts_bspline_to_beziers(&spline, &beziers, NULL) ) {
   *          ... An error occurred ...
   *     }
   *
   *   It is of course possible to check the actual type of error:
   *
   *       tsError error = ts_bspline_to_beziers(&spline, &beziers, NULL);
   *       if (error == TS_MALLOC) {
   *            ... Out of memory ...
   *       } else if (error == ...
   *
   *   This type of error handling is used in many C programs. The disadvantage
   *   is that there is no additional error message besides the error code (with
   *   which the cause of an error could be specified in more detail). Some
   *   libraries make do with global variables in which error messages are stored
   *   for later purpose (e.g., \a errno and \a strerror). Unfortunately,
   *   however, this approach (by design) is often not thread-safe. The second
   *   error handling option solves this issue.
   *
   * 2. ::tsStatus objects: Functions that can fail do not only return an error
   * code, but also take a pointer to a ::tsStatus object as an optional
   * parameter. In the event of an error, and if the supplied pointer is not
   * NULL, the error message is stored in tsStatus#message and can be accessed by
   * the caller. Using a ::tsStatus object, the example given in 1. can be
   * modified as follows:
   *
   *     tsStatus status;
   *     if ( ts_bspline_to_beziers(&spline, &beziers, &status) ) {
   *         status.code; // error code
   *         status.message; // error message
   *     }
   *
   *   Note that ::tsStatus objects can be reused:
   *
   *       tsStatus status;
   *       if ( ts_bspline_to_beziers(&spline, &beziers, &status) ) {
   *           ...
   *       }
   *       ...
   *       if ( ts_bspline_derive(&beziers, 1, 0.001, &beziers, &status) ) {
   *           ...
   *       }
   *
   *   If you would like to use this type of error handling in your own functions
   *   (in particular the optional ::tsStatus parameter), you may wonder whether
   *   there is an easy way to return error codes and format error messages. This
   *   is where the macros ::TS_RETURN_0 -- ::TS_RETURN_4 come into play. They
   *   can, for example, be used as follows:
   *
   *       tsError my_function(..., tsStatus *status, ...)
   *       {
   *           ...
   *           tsReal *points = (tsReal *) malloc(len * sizeof(tsReal));
   *           if (!points)
   *               TS_RETURN_0(status, TS_MALLOC, "out of memory")
   *           ...
   *       }
   *
   *   The \c N in \c TS_RETURN_<N> denotes the number of format specifier in the
   *   supplied format string (cf. sprintf(char *, const char *, ... )).
   *
   * 3. Try-catch-finally blocks: TinySpline provides a set of macros that can be
   * used when a complex control flow is necessary. The macros create a structure
   * that is similar to the exception handling mechanisms of high-level languages
   * such as C++. The basic structure is as follows:
   *
   *     TS_TRY(try, error, status) // `status' may be NULL
   *         ...
   *         TS_CALL( try, error, ts_bspline_to_beziers(
   *             &spline, &beziers, status) )
   *         ...
   *     TS_CATCH(error)
   *         ... Executed in case of an error ...
   *         ... `error' (tsError) indicates the type of error.
   *         ... `status' (tsStatus) contains the error code and message ...
   *     TS_FINALLY
   *         ... Executed in any case ...
   *     TS_END_TRY
   *
   *   ::TS_TRY and ::TS_END_TRY mark the boundaries of a try-catch-finally
   *   block. Every block has an identifier (name) that must be unique within a
   *   scope. The name of a block is set via the first argument of ::TS_TRY (\c
   *   try in the example listed above). The control flow of a try-catch-finally
   *   block is directed via the second and third argument of ::TS_TRY (\c error
   *   and \c status in the example listed above) and the utility macro
   *   ::TS_CALL. The second argument of ::TS_TRY, a ::tsError, is mandatory. The
   *   third argument of ::TS_TRY, a ::tsStatus object, is optional, that is, it
   *   may be \c NULL. ::TS_CALL serves as a wrapper for functions with return
   *   type ::tsError. If the called functions fails (more on that later),
   *   ::TS_CALL immediately jumps into the ::TS_CATCH section where \c error and
   *   \c status can be evaluated as needed (note that \c status may be \c
   *   NULL). The ::TS_FINALLY section is executed in any case and is in
   *   particularly helpful when resources (such as heap-memory, file-handles
   *   etc.) must be released.
   *
   *   While ::TS_CALL can be used to wrap functions with return type ::tsError,
   *   sooner or later it will be necessary to delegate the failure of other
   *   kinds of functions (i.e., functions outside of TinySpline; e.g.,
   *   malloc(size_t)). This is the purpose of the ::TS_THROW_0 -- ::TS_THROW_4
   *   macros. It is not by coincidence that the signature of the \c TS_THROW_<N>
   *   macros is quite similar to that of the \c TS_RETURN_<N> macros. Both
   *   "macro groups" are used to report errors. The difference between \c
   *   TS_RETURN_<N> and TS_THROW_<N>, however, is that the former exits a
   *   function (i.e., a \c return statement is inserted by these macros) while
   *   the latter jumps into a catch block (the catch block to jump into is set
   *   via the first argument of \c TS_THROW_<N>):
   *
   *       tsBSpline spline = ts_bspline_init();
   *       tsReal *points = NULL;
   *       TS_TRY(try, error, status)
   *           ...
   *           tsReal *points = (tsReal *) malloc(len * sizeof(tsReal));
   *           if (!points)
   *               TS_THROW_0(try, status, TS_MALLOC, "out of memory")
   *           ...
   *           TS_CALL( try, error, ts_bspline_interpolate_cubic_natural(
   *               points, len / dim, dim, &spline, status) )
   *           ...
   *       TS_CATCH(error)
   *           ... Log error message ...
   *       TS_FINALLY
   *           ts_bspline_free(&spline);
   *           if (points)
   *               free(points);
   *       TS_END_TRY
   *
   *   In all likelihood, you are already familiar with this kind error
   *   handling. Actually, there are a plethora of examples available online
   *   showing how exception-like error handling can be implemented in C. What
   *   most of these examples have in common is that they suggest to wrap the
   *   functions \c setjmp and \c longjmp (see setjmp.h) with macros. While this
   *   undoubtedly is a clever trick, \c setjmp and \c longjmp have no viable
   *   (i.e, thread-safe) solution for propagating the cause of an error (in the
   *   form of a human-readable error message) back to the client of a
   *   library. Therefore, TinySpline implements try-catch-finally blocks with \c
   *   if statements, labels, and \c goto statements (TS_THROW_<N>).
   *
   *   ::TS_TRY is flexible enough to be used in functions that are in turn
   *   embedded into TinySpline's error handling system:
   *
   *       tsError my_function(..., tsStatus *status, ...)
   *       {
   *           tsError error;
   *           TS_TRY(try, error, status)
   *               ...
   *           TS_END_TRY
   *           return error;
   *       }
   *
   *   as well as functions forming the root of a call stack that uses
   *   TinySpline's error handling system:
   *
   *       tsStatus status;
   *       TS_TRY(try, status.code, &status)
   *           ...
   *       TS_END_TRY
   *
   *   There is some utility macros that might be useful when dealing with
   *   try-catch-finally blocks:
   *
   *   - ::TS_END_TRY_RETURN: Returns the supplied error code immediately after
   *     completing the corresponding block. Can be used as follows:
   *
   *         tsError my_function(..., tsStatus *status, ...)
   *         {
   *             tsError error;
   *             TS_TRY(try, error, status)
   *                 ...
   *             TS_END_TRY_RETURN(error)
   *         }
   *
   *   - ::TS_END_TRY_ROE: Like ::TS_END_TRY_RETURN but returns the supplied
   *     error code, \c e, if \c e is not ::TS_SUCCESS (\c ROE means
   *     <b>R</b>eturn <b>O</b>n <b>E</b>rror). Can be used as follows:
   *
   *         tsError my_function(..., tsStatus *status, ...)
   *         {
   *             tsError error;
   *             TS_TRY(try, error, status)
   *                 ...
   *             TS_END_TRY_ROE(error)
   *             ... Additional code. The code is executed only if `error' is
   *                 TS_SUCCESS, that is, if no error occurred in the try block
   *                 above ...
   *         }
   *
   *   - ::TS_CALL_ROE: Calls the supplied function and returns its error code,
   *     \c e, if \c e is not ::TS_SUCCESS. This macro can be seen as a \e
   *     minified try block (a dedicated try block is not needed).
   *
   *   - ::TS_RETURN_SUCCESS: Shortcut for ::TS_RETURN_0 with error code
   *     ::TS_SUCCESS and an empty error message.
   *
   * @{
   */
  /**
   * Defines different error codes.
   */
  typedef enum
  {
  	/** No error. */
  	TS_SUCCESS = 0,
  
  	/** Memory cannot be allocated (malloc, realloc etc.). */
  	TS_MALLOC = -1,
  
  	/** Points have dimensionality 0. */
  	TS_DIM_ZERO = -2,
  
  	/** degree >= num(control_points). */
  	TS_DEG_GE_NCTRLP = -3,
  
  	/** Knot is not within the domain. */
  	TS_U_UNDEFINED = -4,
  
  	/** multiplicity(knot) > order */
  	TS_MULTIPLICITY = -5,
  
  	/** Decreasing knot vector. */
  	TS_KNOTS_DECR = -6,
  
  	/** Unexpected number of knots. */
  	TS_NUM_KNOTS = -7,
  
  	/** Spline is not derivable. */
  	TS_UNDERIVABLE = -8,
  
  	/** len(control_points) % dimension != 0. */
  	TS_LCTRLP_DIM_MISMATCH = -10,
  
  	/** Error while reading/writing a file. */
  	TS_IO_ERROR = -11,
  
  	/** Error while parsing a serialized entity. */
  	TS_PARSE_ERROR = -12,
  
  	/** Index does not exist (e.g., when accessing an array). */
  	TS_INDEX_ERROR = -13,
  
  	/** Function returns without result (e.g., approximations). */
  	TS_NO_RESULT = -14,
  
  	/** Unexpected number of points. */
  	TS_NUM_POINTS = -15
  } tsError;
  
  /**
   * Stores an error code (see ::tsError) with corresponding message.
   */
  typedef struct
  {
  	/** The error code. */
  	tsError code;
  
  	/**
  	 * The corresponding error message (encoded as C string). Memory is
  	 * allocated on stack so as to be able to provide a meaningful message
  	 * in the event of memory issues (cf. ::TS_MALLOC).
  	 */
  	char message[100];
  } tsStatus;
  
  #define TS_TRY(label, error, status)         \
  {                                            \
  	(error) = TS_SUCCESS;                \
  	if ((status) != NULL) {              \
  		(status)->code = TS_SUCCESS; \
  		(status)->message[0] = '\0'; \
  	}                                    \
  	__ ## label ## __:                   \
  	if (!(error)) {
  
  #define TS_CALL(label, error, call)                  \
  		(error) = (call);                    \
  		if ((error)) goto __ ## label ## __;
  
  #define TS_CATCH(error) \
  	} if ((error)) {
  
  #define TS_FINALLY \
  	} {
  
  #define TS_END_TRY \
  	}          \
  }
  
  #define TS_END_TRY_RETURN(error)   \
  	TS_END_TRY return (error);
  
  #define TS_END_TRY_ROE(error)                 \
  	TS_END_TRY if ((error)) return error;
  
  #define TS_CALL_ROE(error, call)   \
  {                                  \
  	(error) = (call);          \
  	if ((error)) return error; \
  }
  
  #define TS_RETURN_SUCCESS(status)            \
  {                                            \
  	if ((status) != NULL) {              \
  		(status)->code = TS_SUCCESS; \
  		(status)->message[0] = '\0'; \
  	}                                    \
  	return TS_SUCCESS;                   \
  }
  
  #define TS_RETURN_0(status, error, msg)          \
  {                                                \
  	if ((status) != NULL) {                  \
  		(status)->code = error;          \
  		sprintf((status)->message, msg); \
  	}                                        \
  	return error;                            \
  }
  
  #define TS_RETURN_1(status, error, msg, arg1)          \
  {                                                      \
  	if ((status) != NULL) {                        \
  		(status)->code = error;                \
  		sprintf((status)->message, msg, arg1); \
  	}                                              \
  	return error;                                  \
  }
  
  #define TS_RETURN_2(status, error, msg, arg1, arg2)          \
  {                                                            \
  	if ((status) != NULL) {                              \
  		(status)->code = error;                      \
  		sprintf((status)->message, msg, arg1, arg2); \
  	}                                                    \
  	return error;                                        \
  }
  
  #define TS_RETURN_3(status, error, msg, arg1, arg2, arg3)          \
  {                                                                  \
  	if ((status) != NULL) {                                    \
  		(status)->code = error;                            \
  		sprintf((status)->message, msg, arg1, arg2, arg3); \
  	}                                                          \
  	return error;                                              \
  }
  
  #define TS_RETURN_4(status, error, msg, arg1, arg2, arg3, arg4)          \
  {                                                                        \
  	if ((status) != NULL) {                                          \
  		(status)->code = error;                                  \
  		sprintf((status)->message, msg, arg1, arg2, arg3, arg4); \
  	}                                                                \
  	return error;                                                    \
  }
  
  #define TS_THROW_0(label, error, status, val, msg) \
  {                                                  \
  	(error) = val;                             \
  	if ((status) != NULL) {                    \
  		(status)->code = val;              \
  		sprintf((status)->message, msg);   \
  	}                                          \
  	goto __ ## label ## __;                    \
  }
  
  #define TS_THROW_1(label, error, status, val, msg, arg1) \
  {                                                        \
  	(error) = val;                                   \
  	if ((status) != NULL) {                          \
  		(status)->code = val;                    \
  		sprintf((status)->message, msg, arg1);   \
  	}                                                \
  	goto __ ## label ## __;                          \
  }
  
  #define TS_THROW_2(label, error, status, val, msg, arg1, arg2) \
  {                                                              \
  	(error) = val;                                         \
  	if ((status) != NULL) {                                \
  		(status)->code = val;                          \
  		sprintf((status)->message, msg, arg1, arg2);   \
  	}                                                      \
  	goto __ ## label ## __;                                \
  }
  
  #define TS_THROW_3(label, error, status, val, msg, arg1, arg2, arg3) \
  {                                                                    \
  	(error) = val;                                               \
  	if ((status) != NULL) {                                      \
  		(status)->code = val;                                \
  		sprintf((status)->message, msg, arg1, arg2, arg3);   \
  	}                                                            \
  	goto __ ## label ## __;                                      \
  }
  
  #define TS_THROW_4(label, error, status, val, msg, arg1, arg2, arg3, arg4) \
  {                                                                          \
  	(error) = val;                                                     \
  	if ((status) != NULL) {                                            \
  		(status)->code = val;                                      \
  		sprintf((status)->message, msg, arg1, arg2, arg3, arg4);   \
  	}                                                                  \
  	goto __ ## label ## __;                                            \
  }
  /*! @} */
  
  
  
  /*! @name Utility Structs and Enums
   *
   * @{
   */
  /**
   * Describes the structure of the knot vector. More details can be found at:
   *
   *     www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/B-spline/bspline-curve.html
   */
  typedef enum
  {
  	/** Uniformly spaced knot vector with opened end knots. */
  	TS_OPENED = 0,
  
  	/** Uniformly spaced knot vector with clamped end knots. */
  	TS_CLAMPED = 1,
  
  	/**
  	 * Uniformly spaced knot vector where the multiplicity of each knot is
  	 * equal to the order of the spline.
  	 */
  	TS_BEZIERS = 2
  } tsBSplineType;
  
  /**
   * A three-dimensional TNB-vector with position. More details can be found at:
   *
   *     https://en.wikipedia.org/wiki/Frenet-Serret_formulas
   *     https://www.math.tamu.edu/~tkiffe/calc3/tnb/tnb.html
   *
   * TNB stands for \e tangent, \e normal, and \e binormal.
   */
  typedef struct
  {
  	/** Position of the TNB-vector. */
  	tsReal position[3];
  
  	/** Tangent of the TNB-vector. */
  	tsReal tangent[3];
  
  	/** Normal of the TNB-vector. */
  	tsReal normal[3];
  
  	/** Binormal of the TNB-vector. */
  	tsReal binormal[3];
  } tsFrame;
  /*! @} */
  
  
  
  /*! @name B-Spline Data
   *
   * The internal state of ::tsBSpline is protected using the PIMPL design
   * pattern (see https://en.cppreference.com/w/cpp/language/pimpl for more
   * details). The data of an instance can be accessed with the functions listed
   * in this section.
   *
   * @{
   */
  /**
   * Represents a B-Spline, which may also be used for NURBS, Bezier curves,
   * lines, and points. NURBS use homogeneous coordinates to store their control
   * points (i.e. the last component of a control point stores the weight).
   * Bezier curves are B-Splines with 'num_control_points == order' and a
   * clamped knot vector, which lets them pass through their first and last
   * control point (a property which does not necessarily apply to B-Splines and
   * NURBS). Lines and points, on that basis, are Bezier curves of degree 1
   * (lines) and 0 (points).
   *
   * Two dimensional control points are stored as follows:
   *
   *     [x_0, y_0, x_1, y_1, ..., x_n-1, y_n-1]
   *
   * Tree dimensional control points are stored as follows:
   *
   *     [x_0, y_0, z_0, x_1, y_1, z_1, ..., x_n-1, y_n-1, z_n-1]
   *
   * ... and so on. As already mentioned, NURBS use homogeneous coordinates to
   * store their control points. For example, a NURBS in 2D stores its control
   * points as follows:
   *
   *     [x_0*w_0, y_0*w_0, w_0, x_1*w_1, y_1*w_1, w_1, ...]
   *
   * where 'w_i' is the weight of the i'th control point.
   */
  typedef struct
  {
  	struct tsBSplineImpl *pImpl; /**< The actual implementation. */
  } tsBSpline;
  
  /**
   * Returns the degree of \p spline.
   *
   * @param[in] spline
   * 	The spline whose degree is read.
   * @return
   * 	The degree of \p spline.
   */
  size_t TINYSPLINE_API
  ts_bspline_degree(const tsBSpline *spline);
  
  /**
   * Returns the order (degree + 1) of \p spline.
   *
   * @param[in] spline
   * 	The spline whose order is read.
   * @return
   * 	The order of \p spline.
   */
  size_t TINYSPLINE_API
  ts_bspline_order(const tsBSpline *spline);
  
  /**
   * Returns the dimensionality of \p spline, that is, the number of components
   * of its control points (::ts_bspline_control_points). One-dimensional splines
   * are possible, albeit their benefit might be questionable.
   *
   * @param[in] spline
   * 	The spline whose dimension is read.
   * @return
   * 	The dimension of \p spline (>= 1).
   */
  size_t TINYSPLINE_API
  ts_bspline_dimension(const tsBSpline *spline);
  
  /**
   * Returns the length of the control point array of \p spline.
   *
   * @param[in] spline
   * 	The spline whose length of the control point array is read.
   * @return
   * 	The length of the control point array of \p spline.
   */
  size_t TINYSPLINE_API
  ts_bspline_len_control_points(const tsBSpline *spline);
  
  /**
   * Returns the number of control points of \p spline.
   *
   * @param[in] spline
   * 	The spline whose number of control points is read.
   * @return
   * 	The number of control points of \p spline.
   */
  size_t TINYSPLINE_API
  ts_bspline_num_control_points(const tsBSpline *spline);
  
  /**
   * Returns the size of the control point array of \p spline. This function may
   * be useful when copying control points using \e memcpy or \e memmove.
   *
   * @param[in] spline
   * 	The spline whose size of the control point array is read.
   * @return
   * 	The size of the control point array of \p spline.
   */
  size_t TINYSPLINE_API
  ts_bspline_sof_control_points(const tsBSpline *spline);
  
  /**
   * Returns the pointer to the control point array of \p spline. Note that the
   * return type of this function is \c const for a reason. Clients should only
   * read the returned array. When suppressing the constness and writing to the
   * array against better knowledge, the client is on its own with regard to the
   * consistency of the internal state of \p spline. If the control points of a
   * spline need to be changed, use ::ts_bspline_control_points to obtain a copy
   * of the control point array and ::ts_bspline_set_control_points to copy the
   * changed values back to the spline.
   *
   * @param[in] spline
   * 	The spline whose pointer to the control point array is returned.
   * @return
   * 	Pointer to the control point array of \p spline.
   */
  const tsReal TINYSPLINE_API *
  ts_bspline_control_points_ptr(const tsBSpline *spline);
  
  /**
   * Returns a deep copy of the control points of \p spline.
   *
   * @param[in] spline
   * 	The spline whose control points are read.
   * @param[out] ctrlp
   * 	The output array. \b Note: It is the responsibility of the client to
   * 	release the allocated memory after use.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  ts_bspline_control_points(const tsBSpline *spline,
                            tsReal **ctrlp,
                            tsStatus *status);
  
  /**
   * Returns the pointer to the control point of \p spline at \p index. Note that
   * the type of the out parameter \p ctrlp is \c const for a reason. Clients
   * should only read the returned array. When suppressing the constness of \p
   * ctrlp and writing to the array against better knowledge, the client is on
   * its own with regard to the consistency of the internal state of \p
   * spline. If one of the control points of a spline needs to be changed, use
   * ::ts_bspline_set_control_points to copy the new control point to the spline.
   *
   * @param[in] spline
   * 	The spline whose pointer to the control point at \p index is returned.
   * @param[in] index
   * 	Zero-based index of the control point to be returned.
   * @param[out] ctrlp
   * 	Pointer to the control point of \p spline at \p index.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_INDEX_ERROR
   * 	If \p index is out of range.
   */
  tsError TINYSPLINE_API
  ts_bspline_control_point_at_ptr(const tsBSpline *spline,
                                  size_t index,
                                  const tsReal **ctrlp,
                                  tsStatus *status);
  
  /**
   * Sets the control points of \p spline. Creates a deep copy of \p ctrlp.
   *
   * @pre
   * 	\p ctrlp has length ::ts_bspline_len_control_points.
   * @param[out] spline
   * 	The spline whose control points are set.
   * @param[in] ctrlp
   * 	The value.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   */
  tsError TINYSPLINE_API
  ts_bspline_set_control_points(tsBSpline *spline,
                                const tsReal *ctrlp,
                                tsStatus *status);
  
  /**
   * Sets the control point of \p spline at \p index. Creates a deep copy of
   * \p ctrlp.
   *
   * @pre
   * 	\p ctrlp has length ::ts_bspline_dimension.
   * @param[out] spline
   * 	The spline whose control point is set at \p index.
   * @param[in] index
   * 	Zero-based index of the control point to be set.
   * @param[in] ctrlp
   * 	The value.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_INDEX_ERROR
   * 	If \p index is out of range.
   */
  tsError TINYSPLINE_API
  ts_bspline_set_control_point_at(tsBSpline *spline,
                                  size_t index,
                                  const tsReal *ctrlp,
                                  tsStatus *status);
  
  /**
   * Returns the number of knots of \p spline.
   *
   * @param[in] spline
   * 	The spline whose number of knots is read.
   * @return
   * 	The number of knots of \p spline.
   */
  size_t TINYSPLINE_API
  ts_bspline_num_knots(const tsBSpline *spline);
  
  /**
   * Returns the size of the knot array of \p spline. This function may be useful
   * when copying knots using \e memcpy or \e memmove.
   *
   * @param[in] spline
   * 	The spline whose size of the knot array is read.
   * @return TS_SUCCESS
   * 	The size of the knot array of \p spline.
   */
  size_t TINYSPLINE_API
  ts_bspline_sof_knots(const tsBSpline *spline);
  
  /**
   * Returns the pointer to the knot vector of \p spline. Note that the return
   * type of this function is \c const for a reason. Clients should only read the
   * returned array. When suppressing the constness and writing to the array
   * against better knowledge, the client is on its own with regard to the
   * consistency of the internal state of \p spline. If the knot vector of a
   * spline needs to be changed, use ::ts_bspline_knots to obtain a copy of the
   * knot vector and ::ts_bspline_set_knots to copy the changed values back to
   * the spline.
   *
   * @param[in] spline
   * 	The spline whose pointer to the knot vector is returned.
   * @return
   * 	Pointer to the knot vector of \p spline.
   */
  const tsReal TINYSPLINE_API *
  ts_bspline_knots_ptr(const tsBSpline *spline);
  
  /**
   * Returns a deep copy of the knots of \p spline.
   *
   * @param[in] spline
   * 	The spline whose knots are read.
   * @param[out] knots
   * 	The output array. \b Note: It is the responsibility of the client to
   * 	release the allocated memory after use.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  ts_bspline_knots(const tsBSpline *spline,
                   tsReal **knots,
                   tsStatus *status);
  
  /**
   * Returns the knot of \p spline at \p index.
   *
   * @param[in] spline
   * 	The spline whose knot is read at \p index.
   * @param[in] index
   * 	Zero-based index of the knot to be read.
   * @param[out] knot
   * 	The output value.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_INDEX_ERROR
   * 	If \p index is out of range.
   */
  tsError TINYSPLINE_API
  ts_bspline_knot_at(const tsBSpline *spline,
                     size_t index,
                     tsReal *knot,
                     tsStatus *status);
  
  /**
   * Sets the knots of \p spline. Creates a deep copy of \p knots.
   *
   * @pre
   * 	\p knots has length ::ts_bspline_num_knots.
   * @param[out] spline
   * 	The spline whose knots are set.
   * @param[in] knots
   * 	The value.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_KNOTS_DECR
   * 	If the knot vector is decreasing.
   * @return TS_MULTIPLICITY
   * 	If there is a knot with multiplicity > order
   */
  tsError TINYSPLINE_API
  ts_bspline_set_knots(tsBSpline *spline,
                       const tsReal *knots,
                       tsStatus *status);
  
  /**
   * Sets the knots of \p spline supplied as varargs. As all splines have at
   * least two knots, the first two knots have a named parameter. Note that, by
   * design of varargs in C, the last named parameter must not be float. Thus,
   * \p knot1 is of type double instead of ::tsReal.
   *
   * @pre
   * 	::ts_bspline_num_knots knots are supplied as varargs.
   * @param[out] spline
   *	The spline whose knots are set.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @param[in] knot0
   * 	The first knot.
   * @param[in] knot1
   * 	the second knot.
   * @param[in] ...
   * 	The remaining knots.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   * @return TS_KNOTS_DECR
   * 	If the knot vector is decreasing.
   * @return TS_MULTIPLICITY
   * 	If there is a knot with multiplicity > order
   */
  tsError TINYSPLINE_API
  ts_bspline_set_knots_varargs(tsBSpline *spline,
                               tsStatus *status,
                               tsReal knot0,
                               double knot1,
                               ...);
  
  /**
   * Sets the knot of \p spline at \p index.
   *
   * @param[in] spline
   * 	The spline whose knot is set at \p index.
   * @param[in] index
   * 	Zero-based index of the knot to be set.
   * @param[in] knot
   * 	The value.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_INDEX_ERROR
   * 	If \p index is out of range.
   * @return TS_KNOTS_DECR
   * 	If setting the knot at \p index results in a decreasing knot vector.
   * @return TS_MULTIPLICITY
   * 	If setting the knot at \p index results in a knot vector containing
   * 	\p knot with multiplicity greater than the order of \p spline.
   */
  tsError TINYSPLINE_API
  ts_bspline_set_knot_at(tsBSpline *spline,
                         size_t index,
                         tsReal knot,
                         tsStatus *status);
  /*! @} */
  
  
  
  /*! @name B-Spline Initialization
   *
   * The following functions are used to create and release ::tsBSpline instances
   * as well as to copy and move the internal data of a ::tsBSpline instance to
   * another instance.
   *
   * \b Note: It is recommended to initialize an instance with
   * ::ts_bspline_init. This way, ::ts_bspline_free can be called in ::TS_CATCH
   * and ::TS_FINALLY blocks (see Section Error Handling for more details)
   * without further checking. For example:
   *
   *     tsBSpline spline = ts_bspline_init();
   *     TS_TRY(...)
   *         ...
   *     TS_FINALLY
   *         ts_bspline_free(&spline);
   *     TS_END_TRY
   *
   * @{
   */
  /**
   * Creates a new spline whose data points to NULL.
   *
   * @return
   * 	A new spline whose data points to NULL.
   */
  tsBSpline TINYSPLINE_API
  ts_bspline_init(void);
  
  /**
   * Creates a new spline and stores the result in \p spline.
   *
   * @param[in] num_control_points
   * 	The number of control points of \p spline.
   * @param[in] dimension
   * 	The dimension of the control points of \p spline.
   * @param[in] degree
   * 	The degree of \p spline.
   * @param[in] type
   * 	How to setup the knot vector of \p spline.
   * @param[out] spline
   * 	The output spline.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_DIM_ZERO
   * 	If \p dimension is \c 0.
   * @return TS_DEG_GE_NCTRLP
   * 	If \p degree >= \p num_control_points.
   * @return TS_NUM_KNOTS
   * 	If \p type is ::TS_BEZIERS and
   * 	(\p num_control_points % \p degree + 1) != 0.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  ts_bspline_new(size_t num_control_points,
                 size_t dimension,
                 size_t degree,
                 tsBSplineType type,
                 tsBSpline *spline,
                 tsStatus *status);
  
  /**
   * Creates a new spline with given control points (varargs) and stores the
   * result in \p spline. As all splines have at least one control point (with
   * minimum dimensionality one), the first component of the first control point
   * has a named parameter. Note that, by design of varargs in C, the last named
   * parameter must not be float. Thus, \p first is of type double instead of
   * ::tsReal.
   *
   * @param[in] num_control_points
   * 	The number of control points of \p spline.
   * @param[in] dimension
   * 	The dimension of the control points of \p spline.
   * @param[in] degree
   * 	The degree of \p spline.
   * @param[in] type
   * 	How to setup the knot vector of \p spline.
   * @param[out] spline
   * 	The output spline.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @param[in] first
   * 	The first component of the first control point.
   * @param[in] ...
   * 	The remaining components (control points).
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_DIM_ZERO
   * 	If \p dimension is \c 0.
   * @return TS_DEG_GE_NCTRLP
   * 	If \p degree >= \p num_control_points.
   * @return TS_NUM_KNOTS
   * 	If \p type is ::TS_BEZIERS and
   * 	(\p num_control_points % \p degree + 1) != 0.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  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,
                                     ...);
  
  /**
   * Creates a deep copy of \p src and stores the copied data in \p dest. \p src
   * and \p dest can be the same instance.
   *
   * \b Note: Unlike \e memcpy and \e memmove, the first parameter is the source
   * and the second parameter is the destination.
   *
   * @param[in] src
   * 	The spline to be deep copied.
   * @param[out] dest
   * 	The output spline.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  ts_bspline_copy(const tsBSpline *src,
                  tsBSpline *dest,
                  tsStatus *status);
  
  /**
   * Moves the ownership of the data of \p src to \p dest. After calling this
   * function, the data of \p src points to NULL. Does not release the data of \p
   * dest. \p src and \p dest can be the same instance (in this case, the data of
   * \p src remains).
   *
   * @param[in, out] src
   * 	The spline whose data is moved to \p dest.
   * @param[out] dest
   * 	The spline that receives the data of \p src.
   */
  void TINYSPLINE_API
  ts_bspline_move(tsBSpline *src,
                  tsBSpline *dest);
  
  /**
   * Releases the data of \p spline. After calling this function, the data of \p
   * spline points to NULL.
   *
   * @param[out] spline
   * 	The spline to be released.
   */
  void TINYSPLINE_API
  ts_bspline_free(tsBSpline *spline);
  /*! @} */
  
  
  
  /*! @name De Boor Net Data
   *
   * The internal state of ::tsDeBoorNet is protected using the PIMPL design
   * pattern (see https://en.cppreference.com/w/cpp/language/pimpl for more
   * details). The data of an instance can be accessed with the functions listed
   * in this section.
   *
   * @{
   */
  /**
   * Represents the output of De Boor's algorithm. De Boor's algorithm is used to
   * evaluate a spline at a certain knot by iteratively computing a net of
   * intermediate points until the resulting point is available:
   *
   *     https://en.wikipedia.org/wiki/De_Boor%27s_algorithm
   *     https://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/de-Boor.html
   *
   * All points of a net are stored in \c points (::ts_deboornet_points). The
   * resulting point is the last point in \c points and, for the sake of
   * convenience, can be accessed with ::ts_deboornet_result.
   *
   * Two dimensional points are stored as follows:
   *
   *     [x_0, y_0, x_1, y_1, ..., x_n-1, y_n-1]
   *
   * Tree dimensional points are stored as follows:
   *
   *     [x_0, y_0, z_0, x_1, y_1, z_1, ..., x_n-1, y_n-1, z_n-1]
   *
   * ... and so on. The output also supports homogeneous coordinates
   * (cf. ::tsBSpline).
   *
   * There is a special case in which the evaluation of a knot \c u returns two
   * results instead of one. It occurs when the multiplicity of \c u (\c s(u)) is
   * equals to the order of the evaluated spline, indicating that the spline is
   * discontinuous at \c u. This is common practice for B-Splines (and NURBS)
   * consisting of connected Bezier curves where the endpoint of curve \c c_i is
   * equal to the start point of curve \c c_i+1. Yet, the end point of \c c_i and
   * the start point of \c c_i+1 may still be completely different, yielding to
   * visible gaps (if distance of the points is large enough). In such case (\c
   * s(u) == \c order), ::ts_deboornet_points stores only the two resulting
   * points (there is no net to calculate) and ::ts_deboornet_result points to
   * the \e first point in ::ts_deboornet_points. Since having gaps in splines is
   * unusual, both points in ::ts_deboornet_points are generally equal, making it
   * easy to handle this special case by simply calling
   * ::ts_deboornet_result. However, one can access both points if necessary:
   *
   *     ts_deboornet_result(...)[0] ...       // Access the first component of
   *                                           // the first result.
   *
   *     ts_deboornet_result(...)[dim(spline)] // Access the first component of
   *                                           // the second result.
   *
   * As if this wasn't complicated enough, there is an exception for this special
   * case, yielding to exactly one result (just like the regular case) even if \c
   * s(u) == \c order. It occurs when \c u is the lower or upper bound of the
   * domain of the evaluated spline. For instance, if \c b is a spline with
   * domain [0, 1] and \c b is evaluated at \c u = \c 0 or \c u = \c 1, then
   * ::ts_deboornet_result is \e always a single point regardless of the
   * multiplicity of \c u.
   *
   * In summary, there are three different types of evaluation:
   *
   * 1. The regular case, in which all points of the net are returned.
   *
   * 2. A special case, in which two results are returned (required for splines
   * with gaps).
   *
   * 3. The exception of 2., in which exactly one result is returned (even if \c
   * s(u) == \c order).
   *
   * All in all this looks quite complex (and actually it is), but for most
   * applications you do not have to deal with this. Just use
   * ::ts_deboornet_result to access the outcome of De Boor's algorithm.
   */
  typedef struct
  {
  	struct tsDeBoorNetImpl *pImpl; /**< The actual implementation. */
  } tsDeBoorNet;
  
  /**
   * Returns the knot (sometimes also referred to as \c u or \c t) of \p net.
   *
   * @param[in] net
   * 	The net whose knot is read.
   * @return
   * 	The knot of \p net.
   */
  tsReal TINYSPLINE_API
  ts_deboornet_knot(const tsDeBoorNet *net);
  
  /**
   * Returns the index of the knot of \p net.
   *
   * @param[in] net
   * 	The net whose index is read.
   * @return
   * 	The index [u_k, u_k+1) with \c u being the knot of \p net.
   */
  size_t TINYSPLINE_API
  ts_deboornet_index(const tsDeBoorNet *net);
  
  /**
   * Returns the multiplicity of the knot of \p net.
   *
   * @param[in] net
   * 	The net whose multiplicity is read.
   * @return
   * 	The multiplicity of the knot of \p net.
   */
  size_t TINYSPLINE_API
  ts_deboornet_multiplicity(const tsDeBoorNet *net);
  
  /**
   * Returns the number of insertion that were necessary to evaluate the knot of
   * \p net.
   *
   * @param[in] net
   * 	The net whose number of insertions of its knot is read.
   * @return
   * 	The number of insertions that were necessary to evaluate the knot of \p
   * 	net.
   */
  size_t TINYSPLINE_API
  ts_deboornet_num_insertions(const tsDeBoorNet *net);
  
  /**
   * Returns the dimensionality of \p net, that is, the number of components of
   * its points (::ts_deboornet_points) and result (::ts_deboornet_result).
   * One-dimensional nets are possible, albeit their benefit might be
   * questionable.
   *
   * @param[in] net
   * 	The net whose dimension is read.
   * @return
   * 	The dimensionality of \p net (>= 1).
   */
  size_t TINYSPLINE_API
  ts_deboornet_dimension(const tsDeBoorNet *net);
  
  /**
   * Returns the length of the point array of \p net.
   *
   * @param[in] net
   * 	The net whose length of the point array is read.
   * @return
   * 	The length of the point array of \p net.
   */
  size_t TINYSPLINE_API
  ts_deboornet_len_points(const tsDeBoorNet *net);
  
  /**
   * Returns the number of points of \p net.
   *
   * @param[in] net
   * 	The net whose number of points is read.
   * @return
   * 	The number of points of \p net.
   */
  size_t TINYSPLINE_API
  ts_deboornet_num_points(const tsDeBoorNet *net);
  
  /**
   * Returns the size of the point array of \p net. This function may be useful
   * when copying points using \e memcpy or \e memmove.
   *
   * @param[in] net
   * 	The net whose size of the point array is read.
   * @return
   * 	The size of the point array of \p net.
   */
  size_t TINYSPLINE_API
  ts_deboornet_sof_points(const tsDeBoorNet *net);
  
  /**
   * Returns the pointer to the point array of \p net. Note that the return type
   * of this function is \c const for a reason. Clients should only read the
   * returned array. When suppressing the constness and writing to the array
   * against better knowledge, the client is on its own with regard to the
   * consistency of the internal state of \p net. To obtain a copy of the points
   * of \p net, use ::ts_deboornet_points.
   *
   * @param[in] net
   * 	The net whose pointer to the point array is returned.
   * @return
   * 	Pointer to the point array of \p net.
   */
  const tsReal TINYSPLINE_API *
  ts_deboornet_points_ptr(const tsDeBoorNet *net);
  
  /**
   * Returns a deep copy of the points of \p net.
   *
   * @param[in] net
   * 	The net whose points are read.
   * @param[out] points
   * 	The output array. \b Note: It is the responsibility of the client to
   * 	release the allocated memory after use.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  ts_deboornet_points(const tsDeBoorNet *net,
                      tsReal **points,
                      tsStatus *status);
  
  /**
   * Returns the length of the result array of \p net.
   *
   * @param[in] net
   * 	The net whose length of the result array is read.
   * @return
   * 	The length of the result array of \p net.
   */
  size_t TINYSPLINE_API
  ts_deboornet_len_result(const tsDeBoorNet *net);
  
  /**
   * Returns the number of points in the result array of \p net
   * (1 <= num_result <= 2).
   *
   * @param[in] net
   * 	The net whose number of points in the result array is read.
   * @return
   * 	The number of points in the result array of \p net.
   */
  size_t TINYSPLINE_API
  ts_deboornet_num_result(const tsDeBoorNet *net);
  
  /**
   * Returns the size of the result array of \p net. This function may be useful
   * when copying results using \e memcpy or \e memmove.
   *
   * @param[in] net
   * 	The net whose size of the result array is read.
   * @return TS_SUCCESS
   * 	The size of the result array of \p net.
   */
  size_t TINYSPLINE_API
  ts_deboornet_sof_result(const tsDeBoorNet *net);
  
  /**
   * Returns the pointer to the result array of \p net. Note that the return type
   * of this function is \c const for a reason. Clients should only read the
   * returned array. When suppressing the constness and writing to the array
   * against better knowledge, the client is on its own with regard to the
   * consistency of the internal state of \p net. To obtain a copy of the result
   * of \p net, use ::ts_deboornet_result.
   *
   * @param[in] net
   * 	The net whose pointer to the result array is returned.
   * @return
   * 	Pointer to the result array of \p net.
   */
  const tsReal TINYSPLINE_API *
  ts_deboornet_result_ptr(const tsDeBoorNet *net);
  
  /**
   * Returns a deep copy of the result of \p net.
   *
   * @param[in] net
   * 	The net whose result is read.
   * @param[out] result
   * 	The output array. \b Note: It is the responsibility of the client to
   * 	release the allocated memory after use.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  ts_deboornet_result(const tsDeBoorNet *net,
                      tsReal **result,
                      tsStatus *status);
  /*! @} */
  
  
  
  /*! @name De Boor Net Initialization
   *
   * The following functions are used to create and release ::tsDeBoorNet
   * instances as well as to copy and move the internal data of a ::tsDeBoorNet
   * instance to another instance.
   *
   * \b Note: It is recommended to initialize an instance with
   * ::ts_deboornet_init. This way, ::ts_deboornet_free can be called in
   * ::TS_CATCH and ::TS_FINALLY blocks (see Section Error Handling for more
   * details) without further checking. For example:
   *
   *     tsDeBoorNet net = ts_deboornet_init();
   *     TS_TRY(...)
   *         ...
   *     TS_FINALLY
   *         ts_deboornet_free(&net);
   *     TS_END_TRY
   *
   * @{
   */
  /**
   * Creates a new net whose data points to NULL.
   *
   * @return
   * 	A new net whose data points to NULL.
   */
  tsDeBoorNet TINYSPLINE_API
  ts_deboornet_init(void);
  
  /**
   * Creates a deep copy of \p src and stores the copied data in \p dest. \p src
   * and \p dest can be the same instance.
   *
   * \b Note: Unlike \e memcpy and \e memmove, the first parameter is the source
   * and the second parameter is the destination.
   *
   * @param[in] src
   * 	The net to be deep copied.
   * @param[out] dest
   * 	The output net.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  ts_deboornet_copy(const tsDeBoorNet *src,
                    tsDeBoorNet *dest,
                    tsStatus *status);
  
  /**
   * Moves the ownership of the data of \p src to \p dest. After calling this
   * function, the data of \p src points to NULL. Does not release the data of \p
   * dest. \p src and \p dest can be the same instance (in this case, the data of
   * \p src remains).
   *
   * @param[out] src
   * 	The net whose data is moved to \p dest.
   * @param[out] dest
   * 	The net that receives the data of \p src.
   */
  void TINYSPLINE_API
  ts_deboornet_move(tsDeBoorNet *src,
                    tsDeBoorNet *dest);
  
  /**
   * Releases the data of \p net. After calling this function, the data of \p net
   * points to NULL.
   *
   * @param[out] net
   * 	The net to be released.
   */
  void TINYSPLINE_API
  ts_deboornet_free(tsDeBoorNet *net);
  /*! @} */
  
  
  
  /*! @name Interpolation and Approximation Functions
   *
   * Given a set (or a sequence) of points, interpolate/approximate a spline that
   * follows these points.
   *
   * Note: Approximations have not yet been implemented. Pull requests are
   * welcome.
   *
   * @{
   */
  /**
   * Interpolates a cubic spline with natural end conditions. For more details
   * see:
   *
   *     https://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm
   *     http://www.math.ucla.edu/~baker/149.1.02w/handouts/dd_splines.pdf
   *     http://www.bakoma-tex.com/doc/generic/pst-bspline/pst-bspline-doc.pdf
   *
   * The interpolated spline is a sequence of bezier curves connecting each point
   * in \p points. Each bezier curve is of degree \c 3 with dimensionality \p
   * dimension. The total number of control points is:
   *
   *     min(1, \p num_points - 1) * 4
   *
   * Note: \p num_points is the number of points in \p points and not the length
   * of \p points. For instance, the following point vector has
   * \p num_points = 4 and \p dimension = 2:
   *
   *     [x0, y0, x1, y1, x2, y2, x3, y3]
   *
   * @param[in] points
   * 	The points to be interpolated.
   * @param[in] num_points
   * 	The number of points in \p points. If \c 1, a cubic point (i.e., a
   * 	spline with four times the same control point) is created.
   * @param[in] dimension
   * 	The dimensionality of the points.
   * @param[out] spline
   * 	The interpolated spline.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_DIM_ZERO
   * 	If \p dimension is 0.
   * @return TS_NUM_POINTS
   * 	If \p num_points is 0.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  ts_bspline_interpolate_cubic_natural(const tsReal *points,
                                       size_t num_points,
                                       size_t dimension,
                                       tsBSpline *spline,
                                       tsStatus *status);
  
  /**
   * Interpolates a piecewise cubic spline by translating the given catmull-rom
   * control points into a sequence of bezier curves. In order to avoid division
   * by zero, successive control points with distance less than or equal to \p
   * epsilon are filtered out. If the resultant sequence contains only a single
   * point, a cubic point (i.e., a spline with four times the same control point)
   * is created. Optionally, the first and last control point can be specified
   * (see \p first and \p last).
   *
   * @param[in] points
   * 	The points to be interpolated.
   * @param[in] num_points
   * 	The number of points in \p points. If \c 1, a cubic point (i.e., a
   * 	spline with four times the same control point) is created.
   * @param[in] dimension
   * 	The dimensionality of the points.
   * @param[in] alpha
   * 	Knot parameterization: 0 => uniform, 0.5 => centripetal, 1 => chordal.
   * 	The input value is clamped to the domain [0, 1].
   * @param[in] first
   * 	The first control point of the catmull-rom sequence. If NULL, an
   * 	appropriate point is generated based on the first two points in
   * 	\p points. If the distance between \p first and the first control point
   * 	in \p points is less than or equals to \p epsilon, \p first is treated
   * 	as NULL. This is necessary to avoid division by zero.
   * @param[in] last
   * 	The last control point of the catmull-rom sequence. If NULL, an
   * 	appropriate point is generated based on the last two points in
   * 	\p points. If the distance between \p last and the last control point
   * 	in \p points is less than or equals to \p epsilon, \p last is treated
   * 	as NULL. This is necessary to avoid division by zero.
   * @param[in] epsilon
   * 	The maximum distance between points with "same" coordinates. That is,
   * 	if the distance between neighboring points is less than or equal to
   * 	\p epsilon, they are considered equal. For the sake of fail-safeness,
   * 	the sign is removed with fabs. It is advisable to pass a value greater
   * 	than zero, however, it is not necessary.
   * @param[out] spline
   * 	The interpolated spline.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_DIM_ZERO
   * 	If \p dimension is 0.
   * @return TS_NUM_POINTS
   * 	If \p num_points is 0.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  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);
  /*! @} */
  
  
  
  /*! @name Query Functions
   *
   * Functions for querying different kinds of data from splines.
   *
   * @{
   */
  /**
   * Evaluates \p spline at \p knot and stores the result (see ::tsDeBoorNet) in
   * \p net.
   *
   * @param[in] spline
   * 	The spline to evaluate.
   * @param[in] knot
   * 	The knot to evaluate \p spline at.
   * @param[out] net
   * 	Stores the evaluation result.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_U_UNDEFINED
   * 	If \p spline is not defined at knot value \p u.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  ts_bspline_eval(const tsBSpline *spline,
                  tsReal knot,
                  tsDeBoorNet *net,
                  tsStatus *status);
  
  /**
   * Evaluates \p spline at each knot in \p knots and stores the evaluated points
   * (see ::ts_deboornet_result) in \p points. If \p knots contains one or more
   * knots where \p spline is discontinuous at, only the first point of the
   * corresponding evaluation result is taken. After calling this function \p
   * points contains exactly \code num * ts_bspline_dimension(spline) \endcode
   * values.
   *
   * This function is in particular useful in cases where a multitude of knots
   * need to be evaluated, because only a single instance of ::tsDeBoorNet is
   * created and reused for all evaluation tasks (therefore the memory footprint
   * is reduced to a minimum).
   *
   * @param[in] spline
   * 	The spline to evaluate.
   * @param[in] knots
   * 	The knot values to evaluate.
   * @param[in] num
   * 	The number of knots in \p us.
   * @param[out] points
   * 	The output parameter.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_U_UNDEFINED
   * 	If \p spline is not defined at one of the knot values in \p us.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  ts_bspline_eval_all(const tsBSpline *spline,
                      const tsReal *knots,
                      size_t num,
                      tsReal **points,
                      tsStatus *status);
  
  /**
   * Generates a sequence of \p num different knots, passes this sequence to
   * ::ts_bspline_eval_all, and stores the resultant points in \p points. The
   * sequence of knots is generated using ::ts_bspline_uniform_knot_seq. If \p
   * num is 0, the default value \c 100 is used as fallback.
   *
   * For the sake of stability regarding future changes, the actual number of
   * generated knots (which only differs from \p num if \p num is 0) is stored in
   * \p actual_num. If \p num is 1, the point located at the minimum of the
   * domain of \p spline is evaluated.
   *
   * @param[in] spline
   * 	The spline to be evaluate.
   * @param[in] num
   * 	The number of knots to be generate.
   * @param[out] points
   * 	The output parameter.
   * @param[out] actual_num
   * 	The actual number of generated knots. Differs from \p num only if
   * 	\p num is 0. Must not be NULL.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  ts_bspline_sample(const tsBSpline *spline,
                    size_t num,
                    tsReal **points,
                    size_t *actual_num,
                    tsStatus *status);
  
  /**
   * Tries to find a point P on \p spline such that:
   *
   *     ts_distance(P[index], value, 1) <= fabs(epsilon)
   *
   * This function is using the bisection method to determine P. Accordingly, it
   * is expected that the control points of \p spline are sorted at component
   * \p index either in ascending order (if \p ascending != 0) or in descending
   * order (if \p ascending == 0). If the control points of \p spline are not
   * sorted at component \p index, the behaviour of this function is undefined.
   * For the sake of fail-safeness, the distance of P[index] and \p value is
   * compared with the absolute value of \p epsilon (using fabs).
   *
   * The bisection method is an iterative approach which minimizes the error
   * (\p epsilon) with each iteration step until an "optimum" was found. However,
   * there may be no point P satisfying the distance condition. Thus, the number
   * of iterations must be limited (\p max_iter). Depending on the domain of the
   * control points of \p spline at component \p index and \p epsilon,
   * \p max_iter ranges from 7 to 50. In most cases \p max_iter == 30 should be
   * fine though. The parameter \p persnickety allows to define the behaviour of
   * this function is case no point was found after \p max_iter iterations. If
   * enabled (!= 0), TS_NO_RESULT is returned. If disabled (== 0), the best
   * fitting point is returned.
   *
   * @param[in] spline
   * 	The spline to evaluate
   * @param[in] value
   * 	The value (point at component \p index) to find.
   * @param[in] epsilon
   * 	The maximum distance (inclusive).
   * @param[in] persnickety
   * 	Indicates whether TS_NO_RESULT should be returned if there is no point
   * 	P satisfying the distance condition (!= 0 to enable, == 0 to disable).
   * 	If disabled, the best fitting point is returned.
   * @param[in] index
   * 	The point's component.
   * @param[in] ascending
   * 	Indicates whether the control points of \p spline are sorted in
   * 	ascending (!= 0) or in descending (== 0) order at component \p index.
   * @param[in] max_iter
   * 	The maximum number of iterations (30 is a sane default value).
   * @param[out] net
   * 	The output parameter.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_INDEX_ERROR
   * 	If the dimension of the control points of \p spline <= \p index.
   * @return TS_NO_RESULT
   * 	If \p persnickety is enabled (!= 0) and there is no point P satisfying
   * 	the distance condition.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  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);
  
  /**
   * Returns the domain of \p spline.
   *
   * @param[in] spline
   * 	The spline to query.
   * @param[out] min
   * 	The lower bound of the domain of \p spline.
   * @param[out] max
   * 	The upper bound of the domain of \p spline.
   */
  void TINYSPLINE_API
  ts_bspline_domain(const tsBSpline *spline,
                    tsReal *min,
                    tsReal *max);
  
  /**
   * Checks whether the distance of the endpoints of \p spline is less than or
   * equal to \p epsilon for the first 'ts_bspline_degree - 1' derivatives
   * (starting with the zeroth derivative).
   *
   * @param[in] spline
   * 	The spline to query.
   * @param[in] epsilon
   * 	The maximum distance.
   * @param[out] closed
   * 	The output parameter. 1 if true, 0 otherwise.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  ts_bspline_is_closed(const tsBSpline *spline,
                       tsReal epsilon,
                       int *closed,
                       tsStatus *status);
  
  /**
   * Computes a sequence of three-dimensional frames (see ::tsFrame) for the
   * spline \p spline. The position of the frames corresponds to the knots in \p
   * knots. The implementation is based on:
   *
   *     @article{10.1145/1330511.1330513,
   *       author =       {Wang, Wenping and J\"{u}ttler, Bert and Zheng, Dayue
   *                       and Liu, Yang},
   *       title =        {Computation of Rotation Minimizing Frames},
   *       year =         {2008},
   *       issue_date =   {March 2008},
   *       publisher =    {Association for Computing Machinery},
   *       address =      {New York, NY, USA},
   *       volume =       {27},
   *       number =       {1},
   *       issn =         {0730-0301},
   *       url =          {https://doi.org/10.1145/1330511.1330513},
   *       doi =          {10.1145/1330511.1330513},
   *       abstract =     {Due to its minimal twist, the rotation minimizing
   *                       frame (RMF) is widely used in computer graphics,
   *                       including sweep or blending surface modeling, motion
   *                       design and control in computer animation and
   *                       robotics, streamline visualization, and tool path
   *                       planning in CAD/CAM. We present a novel simple and
   *                       efficient method for accurate and stable computation
   *                       of RMF of a curve in 3D. This method, called the
   *                       double reflection method, uses two reflections to
   *                       compute each frame from its preceding one to yield a
   *                       sequence of frames to approximate an exact RMF. The
   *                       double reflection method has the fourth order global
   *                       approximation error, thus it is much more accurate
   *                       than the two currently prevailing methods with the
   *                       second order approximation error—the projection
   *                       method by Klok and the rotation method by
   *                       Bloomenthal, while all these methods have nearly the
   *                       same per-frame computational cost. Furthermore, the
   *                       double reflection method is much simpler and faster
   *                       than using the standard fourth order Runge-Kutta
   *                       method to integrate the defining ODE of the RMF,
   *                       though they have the same accuracy. We also
   *                       investigate further properties and extensions of the
   *                       double reflection method, and discuss the
   *                       variational principles in design moving frames with
   *                       boundary conditions, based on RMF.},
   *       journal =      {ACM Trans. Graph.},
   *       month =        mar,
   *       articleno =    {2},
   *       numpages =     {18},
   *       keywords =     {motion design, sweep surface, motion, differential
   *                       geometry, Curve, rotation minimizing frame}
   *     }
   *
   * @pre \p knots and \p frames have \p num entries.
   * @param[in] spline
   * 	The spline to query.
   * @param[in] knots
   * 	The knots to query \p spline at.
   * @param[in] num
   * 	Number of elements in \p knots and \p frames. Can be \c 0.
   * @param[in] has_first_normal
   * 	Indicates whether the normal of the first element of \p frames should
   * 	be taken as starting value for the algorithm. If \c 0, the starting
   * 	normal is determined based on the tangent of \p spline at \c knots[0].
   * 	Note that, if the argument value is not \c 0, it is up to the caller of
   * 	this function to ensure that the supplied normal is valid. The function
   * 	only normalizes the supplied value.
   * @param[in, out] frames
   * 	Stores the computed frames.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_MALLOC
   * 	If memory allocation failed.
   */
  tsError TINYSPLINE_API
  ts_bspline_compute_rmf(const tsBSpline *spline,
                         const tsReal *knots,
                         size_t num,
                         int has_first_normal,
                         tsFrame *frames,
                         tsStatus *status);
  
  /**
   * Generates a sequence of \p num knots with uniform distribution. \e Uniform
   * means that consecutive knots have the same distance.
   *
   * @param[in] spline
   * 	The spline to query.
   * @param[in] num
   * 	Number of knots in \p knots.
   * @param[out] knots
   * 	Stores the generated knot sequence.
   */
  void TINYSPLINE_API
  ts_bspline_uniform_knot_seq(const tsBSpline *spline,
                              size_t num,
                              tsReal *knots);
  /*! @} */
  
  
  
  /*! @name Transformation Functions
   *
   * A transformation modifies the internal state of a spline, e.g., its number
   * of control points, the structure of its knot vector, its degree, and so
   * on. It should be noted that some transformation modify a spline's state
   * without changing its shape (e.g., ::ts_bspline_elevate). All transformations
   * specify at least three parameters: i) an input spline (the spline to be
   * transformed), ii) an output spline (the spline which receives the result of
   * the transformation), and iii) a ::tsStatus (output parameter for error
   * handling). Along with these parameters, additional parameters may be
   * necessary to i) calculate a certain transformation (such as
   * ::ts_bspline_tension) or ii) to store additional results (such as
   * ::ts_bspline_insert_knot). Unless stated otherwise, the order of the
   * parameters of a transformation \c t is:
   *
   *     t(input, [additional_input], output, [additional_output], status)
   *
   * \b Note: None of the transformations releases the memory of the output
   * spline before assigning the transformation result to it. Thus, when using
   * the same output spline multiple times, make sure to release its memory
   * before each call (after the first one). If not, severe memory leaks are to
   * be expected:
   *
   *     tsBSpline in = ...                   // an arbitrary spline
   *     tsBSpline out = ts_bspline_init();   // stores the result
   *
   *     ts_bspline_to_beziers(&in, &out);    // first transformation
   *     ...                                  // some code
   *     ts_bspline_free(&out);               // avoid memory leak.
   *     ts_bspline_tension(&in, 0.85, &out); // next transformation
   *
   * It is possible to pass a spline as input and output argument at the same
   * time. In this case, the called transformation uses a temporary buffer to
   * store the working data and result. If the transformation succeeds, the
   * memory of the supplied spline is released and the transformation result is
   * assigned to it. So even if a transformation fails, the internal state of the
   * supplied splines stays intact (i.e., it remains unchanged).
   *
   * \b Note: It is not necessary to release the memory of a spline which is
   * passed as input and output argument at the same time before calling the next
   * transformation (in fact that would fail due to a null pointer):
   *
   *     tsBSpline spline = ...                      // an arbitrary spline
   *     ts_bspline_to_beziers(&spline, &spline);    // first transformation
   *     ts_bspline_tension(&spline, 0.85, &spline); // next transformation
   *
   * @{
   */
  /**
   * Returns the \p n'th derivative of \p spline as ::tsBSpline instance. The
   * derivative of a spline \c s of degree \c d (\c d > 0) with \c m control
   * points and \c n knots is another spline \c s' of degree \c d-1 with \c m-1
   * control points and \c n-2 knots, defined over \c s as:
   *
   * \f{eqnarray*}{
   *   s'(u) &=& \sum_{i=0}^{n-1} N_{i+1,p-1}(u)    *
   *                              (P_{i+1} - P_{i}) *
   *                              p / (u_{i+p+1}-u_{i+1}) \\
   *         &=& \sum_{i=1}^{n} N_{i,p-1}(u)      *
   *                            (P_{i} - P_{i-1}) *
   *                            p / (u_{i+p}-u_{i})
   * \f}
   *
   * If \c s has a clamped knot vector, it can be shown that:
   *
   * \f{eqnarray*}{
   *   s'(u) &=& \sum_{i=0}^{n-1} N_{i,p-1}(u)      *
   *                              (P_{i+1} - P_{i}) *
   *                              p / (u_{i+p+1}-u_{i+1})
   * \f}
   *
   * where the multiplicity of the first and the last knot value \c u is \c p
   * rather than \c p+1. The derivative of a point (degree 0) is another point
   * with coordinate 0. For more details, see:
   *
   *     http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/B-spline/bspline-derv.html
   *
   * If \p spline != \p deriv, the internal state of \p spline is not modified,
   * that is, \p deriv is a new, independent ::tsBSpline instance.
   *
   * @param[in] spline
   * 	The spline to be derived.
   * @param[in] n
   * 	Number of derivations.
   * @param[in] epsilon
   * 	The maximum distance of discontinuous points. If negative,
   * 	discontinuity is ignored and the derivative is computed based on the
   * 	first result of the corresponding ::tsDeboorNet.
   * @param[out] deriv
   *	The derivative of \p spline.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_UNDERIVABLE
   * 	If \p spline is discontinuous at an internal knot and the distance
   * 	between the corresponding points is greater than \p epsilon.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  ts_bspline_derive(const tsBSpline *spline,
                    size_t n,
                    tsReal epsilon,
                    tsBSpline *deriv,
                    tsStatus *status);
  
  /**
   * Inserts \p knot \p num times into the knot vector of \p spline. The
   * operation fails if \p result would have an invalid knot vector (i.e.,
   * multiplicity(knot) > order(result)). If \p spline != \p result, the internal
   * state of \p spline is not modified, that is, \p result is a new, independent
   * ::tsBSpline instance.
   *
   * @param[in] spline
   * 	The spline into which \p knot is inserted \p num times.
   * @param[in] knot
   * 	The knot to be inserted.
   * @param[in] num
   * 	Number of insertions.
   * @param[out] result
   * 	The output spline.
   * @param[out] k
   * 	Stores the last index of \p knot in \p result.
   * @param status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_U_UNDEFINED
   * 	If \p knot is not within the domain of \p spline.
   * @return TS_MULTIPLICITY
   * 	If the multiplicity of \p knot in \p spline plus \p num is greater than
   * 	the order of \p spline.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  ts_bspline_insert_knot(const tsBSpline *spline,
                         tsReal knot, size_t num,
                         tsBSpline *result,
                         size_t *k,
                         tsStatus *status);
  
  /**
   * Splits \p spline at \p knot. That is, \p knot is inserted into \p spline \c
   * n times such that the multiplicity of \p knot is equal the spline's order.
   * If \p spline != \p split, the internal state of \p spline is not modified,
   * that is, \p split is a new, independent ::tsBSpline instance.
   *
   * @param[in] spline
   * 	The spline to be split.
   * @param[in] knot
   * 	The split point (knot).
   * @param[out] split
   * 	The split spline.
   * @param[out] k
   * 	Stores the last index of \p knot in \p split.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_U_UNDEFINED
   * 	If \p spline is not defined at \p knot.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  ts_bspline_split(const tsBSpline *spline,
                   tsReal knot,
                   tsBSpline *split,
                   size_t *k,
                   tsStatus *status);
  
  /**
   * Straightens the control points of \p spline according to \p beta (0 yields a
   * line connecting the first and the last control point; 1 keeps the original
   * shape). The value of \p beta is clamped to [0, 1]. If \p spline != \p out,
   * the internal state of \p spline is not modified, that is, \p out is a new,
   * independent ::tsBSpline instance.
   *
   * This function is based on:
   *
   *     @ARTICLE{10.1109/TVCG.2006.147,
   *       author =       {Holten, Danny},
   *       journal =      {IEEE Transactions on Visualization and Computer
   *                       Graphics},
   *       title =        {Hierarchical Edge Bundles: Visualization of
   *                       Adjacency Relations in Hierarchical Data},
   *       year =         {2006},
   *       volume =       {12},
   *       number =       {5},
   *       pages =        {741-748},
   *       abstract =     {A compound graph is a frequently encountered type of
   *                       data set. Relations are given between items, and a
   *                       hierarchy is defined on the items as well. We
   *                       present a new method for visualizing such compound
   *                       graphs. Our approach is based on visually bundling
   *                       the adjacency edges, i.e., non-hierarchical edges,
   *                       together. We realize this as follows. We assume that
   *                       the hierarchy is shown via a standard tree
   *                       visualization method. Next, we bend each adjacency
   *                       edge, modeled as a B-spline curve, toward the
   *                       polyline defined by the path via the inclusion edges
   *                       from one node to another. This hierarchical bundling
   *                       reduces visual clutter and also visualizes implicit
   *                       adjacency edges between parent nodes that are the
   *                       result of explicit adjacency edges between their
   *                       respective child nodes. Furthermore, hierarchical
   *                       edge bundling is a generic method which can be used
   *                       in conjunction with existing tree visualization
   *                       techniques. We illustrate our technique by providing
   *                       example visualizations and discuss the results based
   *                       on an informal evaluation provided by potential
   *                       users of such visualizations.},
   *       keywords =     {},
   *       doi =          {10.1109/TVCG.2006.147},
   *       ISSN =         {1941-0506},
   *       month =        {Sep.},
   *     }
   *
   * Holten calls it "straightening" (page 744, equation 1).
   *
   * @param[in] spline
   * 	The spline to be straightened.
   * @param[in] beta
   * 	The straightening factor. The value is clamped to the domain [0, 1].
   * @param[out] out
   * 	The straightened spline.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  ts_bspline_tension(const tsBSpline *spline,
                     tsReal beta,
                     tsBSpline *out,
                     tsStatus *status);
  
  /**
   * Decomposes \p spline into a sequence of Bezier curves by splitting it at
   * each internal knot. If \p spline != \p beziers, the internal state of \p
   * spline is not modified, that is, \p beziers is a new, independent
   * ::tsBSpline instance.
   *
   * @param[in] spline
   * 	The spline to be decomposed.
   * @param[out] beziers
   * 	The bezier decomposition of \p spline.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  ts_bspline_to_beziers(const tsBSpline *spline,
                        tsBSpline *beziers,
                        tsStatus *status);
  
  /**
   * Elevates the degree of \p spline by \p amount and stores the result in
   * \p elevated. If \p spline != \p elevated, the internal state of \p spline is
   * not modified, that is, \p elevated is a new, independent ::tsBSpline
   * instance.
   *
   * @param[in] spline
   * 	The spline to elevate.
   * @param[in] amount
   * 	How often to elevate the degree of \p spline.
   * @param[in] epsilon
   * 	In order to elevate the degree of a spline, it must be decomposed into
   * 	a sequence of bezier curves (see ::ts_bspline_to_beziers). After degree
   * 	elevation, the split points of the bezier curves are merged again. This
   * 	parameter is used to distinguish between the split points of the
   * 	decomposition process and the wanted discontinuity points. A viable
   * 	default value is ::TS_POINT_EPSILON.
   * @param[out] elevated
   * 	The elevated spline.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_MALLOC
   * 	If memory allocation failed.
   */
  tsError TINYSPLINE_API
  ts_bspline_elevate_degree(const tsBSpline *spline,
                            size_t amount,
                            tsReal epsilon,
                            tsBSpline *elevated,
                            tsStatus *status);
  
  /**
   * Modifies the splines \p s1 and \p s2 such that they have same degree and
   * number of control points/knots (without modifying the shape of \p s1 and
   * \p s2). The resulting splines are stored in \p s1_out and \p s2_out. If
   * \p s1 != \p s1_out, the internal state of \p s1 is not modified, that is,
   * \p s1_out is a new, independent ::tsBSpline instance. The same is true for
   * \p s2 and \p s2_out.
   *
   * @param[in] s1
   * 	The spline which is to be aligned with \p s2.
   * @param[in] s2
   * 	The spline which is to be aligned with \p s1.
   * @param[in] epsilon
   * 	Spline alignment relies on degree elevation. This parameter is used in
   * 	::ts_bspline_elevate_degree to check whether two control points, \c p1
   * 	and \c p2, are "equal", that is, the distance between \c p1 and \c p2
   * 	is less than or equal to \p epsilon. A viable default value is
   * 	::TS_POINT_EPSILON.
   * @param[out] s1_out
   * 	The aligned version of \p s1.
   * @param[out] s2_out
   * 	The aligned version of \p s2.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_MALLOC
   * 	If memory allocation failed.
   */
  tsError TINYSPLINE_API
  ts_bspline_align(const tsBSpline *s1,
                   const tsBSpline *s2,
                   tsReal epsilon,
                   tsBSpline *s1_out,
                   tsBSpline *s2_out,
                   tsStatus *status);
  
  /**
   * Interpolates between \p origin and \p target with respect to the time
   * parameter \p t (domain: [0, 1]; clamped if necessary). The resulting spline
   * is stored in \p out. Because it is to be expected that this function is
   * called several times in a row (e.g., to have a smooth transition from one
   * spline to another), memory for \p out is allocated only if it points to NULL
   * or if it has to be enlarged to store the result of the interpolation (which
   * can only happen if \p origin or \p target---or both---have been changed
   * since the last call). This way, this function can be used as follows:
   *
   *     tsReal t;
   *     tsBSpline origin = ...
   *     tsBSpline target = ...
   *     tsBSpline morph = ts_bspline_init();
   *     for (t = (tsReal) 0.0; t <= (tsReal) 1.0; t += (tsReal) 0.001)
   *         ts_bspline_morph(&origin, &target, t, ..., &morph, ...);
   *     ts_bspline_free(&morph);
   *
   * It should be noted that this function, if necessary, aligns \p origin and \p
   * target using ::ts_bspline_align. In order to avoid the overhead of spline
   * alignment, \p origin and \p target should be aligned in advance.
   *
   * @param[in] origin
   * 	Origin spline.
   * @param[in] target
   * 	Target spline.
   * @param[in] t
   * 	The time parameter. If 0, \p out becomes \p origin. If 1, \p out becomes
   * 	\p target. Note that the value passed is clamped to the domain [0, 1].
   * @param[in] epsilon
   * 	If \p origin and \p target must be aligned, this parameter is passed
   * 	::ts_bspline_elevate_degree to check whether two control points, \c p1
   * 	and \c p2, are "equal", that is, the distance between \c p1 and \c p2
   * 	is less than or equal to \p epsilon. A viable default value is
   * 	::TS_POINT_EPSILON.
   * @param[out] out
   * 	The resulting spline.
   * @return[out] TS_SUCCESS
   * 	On success.
   * @return TS_MALLOC
   * 	If memory allocation failed.
   */
  tsError TINYSPLINE_API
  ts_bspline_morph(const tsBSpline *origin,
                   const tsBSpline *target,
                   tsReal t,
                   tsReal epsilon,
                   tsBSpline *out,
                   tsStatus *status);
  /*! @} */
  
  
  
  /*! @name Serialization and Persistence
   *
   * The following functions can be used to serialize and persist (i.e., store
   * the serialized data in a file) splines. There are also functions to load
   * serialized splines.
   *
   * @{
   */
  /**
   * Serializes \p spline to a null-terminated JSON string and stores the result
   * in \p json.
   *
   * @param[in] spline
   * 	The spline to be serialized.
   * @param[out] json
   * 	The serialized JSON string.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  ts_bspline_to_json(const tsBSpline *spline,
                     char **json,
                     tsStatus *status);
  
  /**
   * Parses \p json and stores the result in \p spline.
   *
   * @param[in] json
   * 	The JSON string to be parsed.
   * @param[out] spline
   * 	The output spline.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_PARSE_ERROR
   * 	If an error occurred while parsing \p json.
   * @return TS_DIM_ZERO
   * 	If the dimension is \c 0.
   * @return TS_LCTRLP_DIM_MISMATCH
   * 	If the length of the control point array modulo dimension is not \c 0.
   * @return TS_DEG_GE_NCTRLP
   * 	If the degree is greater or equals to the number of control points.
   * @return TS_NUM_KNOTS
   * 	If the number of knots does not match to the number of control points
   * 	plus the degree of the spline.
   * @return TS_KNOTS_DECR
   * 	If the knot vector is decreasing.
   * @return TS_MULTIPLICITY
   * 	If there is a knot with multiplicity greater than order.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  ts_bspline_parse_json(const char *json,
                        tsBSpline *spline,
                        tsStatus *status);
  
  /**
   * Saves \p spline as JSON ASCII file.
   *
   * @param[in] spline
   * 	The spline to be saved.
   * @param[in] path
   * 	Path of the JSON file.
   * @param[out] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_IO_ERROR
   * 	If an error occurred while saving \p spline.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  ts_bspline_save(const tsBSpline *spline,
                  const char *path,
                  tsStatus *status);
  
  /**
   * Loads \p spline from a JSON ASCII file.
   *
   * @param[in] path
   * 	Path of the JSON file to be loaded.
   * @param[out] spline
   * 	The output spline.
   * @param[ou] status
   * 	The status of this function. May be NULL.
   * @return TS_SUCCESS
   * 	On success.
   * @return TS_IO_ERROR
   * 	If \p path does not exist.
   * @return TS_PARSE_ERROR
   * 	If an error occurred while parsing the contents of \p path.
   * @return TS_DIM_ZERO
   * 	If the dimension is \c 0.
   * @return TS_LCTRLP_DIM_MISMATCH
   * 	If the length of the control point array modulo dimension is not \c 0.
   * @return TS_DEG_GE_NCTRLP
   * 	If the degree is greater or equals to the number of control points.
   * @return TS_NUM_KNOTS
   * 	If the number of knots does not match to the number of control points
   * 	plus the degree of the spline.
   * @return TS_KNOTS_DECR
   * 	If the knot vector is decreasing.
   * @return TS_MULTIPLICITY
   * 	If there is a knot with multiplicity greater than order.
   * @return TS_MALLOC
   * 	If allocating memory failed.
   */
  tsError TINYSPLINE_API
  ts_bspline_load(const char *path,
                  tsBSpline *spline,
                  tsStatus *status);
  
  
  
  /*! @name Vector Math
   *
   * Vector math is a not insignificant part of TinySpline, and so it's not
   * surprising that some utility functions around vectors are needed. Because
   * these functions might be useful for others, they are part of TinySpline's
   * public API. However, note that the code is \b not highly optimized (with,
   * for example, instruction set extensions like SSE). If high performance
   * vector math is needed, other libraries should be used instead.
   *
   * @{
   */
  /**
   * Initializes vector \p out with \p x and \p y.
   *
   * @pre
   * 	\p out has dimensionality \c 2.
   * @param[out] out
   * 	Target vector.
   * @param[in] x
   * 	The x value.
   * @param[in] y
   * 	The y value.
   */
  void TINYSPLINE_API
  ts_vec2_init(tsReal *out,
               tsReal x,
               tsReal y);
  
  /**
   * Initializes vector \p out with \p x, \p y, and \p z.
   *
   * @pre
   * 	\p out has dimensionality \c 3.
   * @param[out] out
   * 	Target vector.
   * @param[in] x
   * 	The x value.
   * @param[in] y
   * 	The y value.
   * @param[in] z
   * 	The z value.
   */
  void TINYSPLINE_API
  ts_vec3_init(tsReal *out,
               tsReal x,
               tsReal y,
               tsReal z);
  
  /**
   * Initializes vector \p out with \p x, \p y, \p z, and \p w.
   *
   * @pre
   * 	\p out has dimensionality \c 4.
   * @param[out] out
   * 	Target vector.
   * @param[in] x
   * 	The x value.
   * @param[in] y
   * 	The y value.
   * @param[in] z
   * 	The z value.
   * @param[in] w
   * 	The w value.
   */
  void TINYSPLINE_API
  ts_vec4_init(tsReal *out,
               tsReal x,
               tsReal y,
               tsReal z,
               tsReal w);
  
  /**
   * Copies the values of vector \p x (a vector with dimensionality \p dim) to
   * vector \p out (a vector with dimensionality \c 2). If \p dim is less than \c
   * 2, the remaining values of \p out are set to \c 0. Excess values in \p x
   * (i.e., \p dim is greater than \c 2) are ignored.
   *
   * @pre
   * 	\p out has dimensionality \c 2.
   * @param[out] out
   * 	Target vector.
   * @param[in] x
   * 	Vector to read the values from.
   * @param[in] dim
   * 	Dimensionality of \p x.
   */
  void TINYSPLINE_API
  ts_vec2_set(tsReal *out,
              const tsReal *x,
              size_t dim);
  
  /**
   * Copies the values of vector \p x (a vector with dimensionality \p dim) to
   * vector \p out (a vector with dimensionality \c 3). If \p dim is less than \c
   * 3, the remaining values of \p out are set to \c 0. Excess values in \p x
   * (i.e., \p dim is greater than \c 3) are ignored.
   *
   * @pre
   * 	\p out has dimensionality \c 3.
   * @param[out] out
   * 	Target vector.
   * @param[in] x
   * 	Vector to read the values from.
   * @param[in] dim
   * 	Dimensionality of \p x.
   */
  void TINYSPLINE_API
  ts_vec3_set(tsReal *out,
              const tsReal *x,
              size_t dim);
  
  /**
   * Copies the values of vector \p x (a vector with dimensionality \p dim) to
   * vector \p out (a vector with dimensionality \c 4). If \p dim is less than \c
   * 4, the remaining values of \p out are set to \c 0. Excess values in \p x
   * (i.e., \p dim is greater than \c 4) are ignored.
   *
   * @pre
   * 	\p out has dimensionality \c 4.
   * @param[out] out
   * 	Target vector.
   * @param[in] x
   * 	Vector to read the values from.
   * @param[in] dim
   * 	Dimensionality of \p x.
   */
  void TINYSPLINE_API
  ts_vec4_set(tsReal *out,
              const tsReal *x,
              size_t dim);
  
  /**
   * Adds vector \p y to vector \p x and stores the result in vector \p out.
   *
   * @param[in] x
   * 	First vector.
   * @param[in] y
   * 	Second vector.
   * @param[in] dim
   * 	Dimensionality of \p x, \p y, and \p out.
   * @param[out] out
   * 	Result vector. Can be same as \p x or \p y, i.e., the result can be
   * 	stored in-place.
   */
  void TINYSPLINE_API
  ts_vec_add(const tsReal *x,
             const tsReal *y,
             size_t dim,
             tsReal *out);
  
  /**
   * Subtracts vector \p y from vector \p x and stores the result in vector \p
   * out.
   *
   * @param[in] x
   * 	First vector.
   * @param[in] y
   * 	Second vector.
   * @param[in] dim
   * 	Dimensionality of \p x, \p y, and \p out.
   * @param[out] out
   * 	Result vector. Can be same as \p x or \p y, i.e., the result can be
   * 	stored in-place.
   */
  void TINYSPLINE_API
  ts_vec_sub(const tsReal *x,
             const tsReal *y,
             size_t dim,
             tsReal *out);
  
  /**
   * Computes the dot product (also known as scalar product) of the vectors \p x
   * and \p y.
   *
   * @post
   * 	\c 0 if \p dim is \c 0.
   * @param[in] x
   * 	First vector.
   * @param[in] y
   * 	Second vector.
   * @param[in] dim
   * 	Dimensionality of \p x and \p y.
   * @return
   * 	The dot product of \p x and \y.
   */
  tsReal TINYSPLINE_API
  ts_vec_dot(const tsReal *x,
             const tsReal *y,
             size_t dim);
  
  /**
   * Computes the angle in degrees between the vectors \p x and \p y. The angle
   * returned is unsigned, that is, the smaller of the two possible angles is
   * computed. The nullable parameter \p buf servers as a buffer in case \p x or
   * \p y (or both) are not normalized. If \p buf is \c NULL, it is expected that
   * \p x and \p y are already normalized. If \p buf is not \c NULL, a storage
   * twice the size of \p dim is expected in which the normalized vectors of \p x
   * and \p y are stored.
   *
   * @pre
   * 	\p buf is either \c NULL or has length <tt>2 * dim</tt>.
   * @param[in] x
   * 	First vector.
   * @param[in] y
   * 	Second vector.
   * @param[out] buf
   * 	A buffer in which the normalized vectors of \p x and \y are stored. If
   * 	\c NULL, it is expected that \p x and \p y are already normalized.
   * @param[in] dim
   * 	Dimensionality of \p x and \p y.
   * @return
   * 	The angle between \p x and \y with <tt>0.0 <= angle <= 180.0</tt>.
   */
  tsReal TINYSPLINE_API
  ts_vec_angle(const tsReal *x,
               const tsReal *y,
               tsReal *buf,
               size_t dim);
  
  /**
   * Computes the cross product (also known as vector product or directed area
   * product) of the vectors \p x and \p y.
   *
   * @pre \p x and \p y have dimensionality \c 3.
   * @param[in] x
   * 	First vector.
   * @param[in] y
   * 	Second vector.
   * @param[out] out
   * 	Result vector. Can be same as \p x or \p y, i.e., the result can be
   * 	stored in-place.
   */
  void TINYSPLINE_API
  ts_vec3_cross(const tsReal *x,
                const tsReal *y,
                tsReal *out);
  
  /**
   * Normalizes vector \p x.
   *
   * @post
   * 	\c 0 if the length of \p x (see ::ts_vec_mag) is less than
   * 	::TS_LENGTH_ZERO.
   * @param[in] x
   * 	A vector.
   * @param[in] dim
   * 	Dimensionality of \p x.
   * @param[out] out
   * 	Result vector. Can be same as \p x, i.e., the result can be stored
   * 	in-place.
   */
  void TINYSPLINE_API
  ts_vec_norm(const tsReal *x,
              size_t dim,
              tsReal *out);
  
  /**
   * Determines the length of vector \p x.
   *
   * @post
   * 	\c 0 if \p dim is \c 0.
   * @param[in] x
   * 	A vector.
   * @param[in] dim
   * 	Dimensionality of \p x.
   */
  tsReal TINYSPLINE_API
  ts_vec_mag(const tsReal *x,
             size_t dim);
  
  /**
   * Multiplies vector \p x with scalar \p val and stores the result in vector \p
   * out.
   *
   * @param[in] x
   * 	A vector.
   * @param[in] dim
   * 	Dimensionality of \p x.
   * @param[in] val
   * 	Scalar value.
   * @param[out] out
   * 	Result vector. Can be same as \p x, i.e., the result can be stored
   * 	in-place.
   */
  void TINYSPLINE_API
  ts_vec_mul(const tsReal *x,
             size_t dim,
             tsReal val,
             tsReal *out);
  /*! @} */
  
  
  
  /*! @name Utility Functions
   *
   * @{
   */
  /**
   * Returns whether the knots \p x and \p y are equal with respect to the epsilon
   * environment ::TS_KNOT_EPSILON (i.e., their distance is <u>less</u> than
   * ::TS_KNOT_EPSILON).
   *
   * @param[in] x
   * 	First knot.
   * @param[in] y
   * 	Second knot.
   * @return 1
   * 	If \p x and \p y are equal.
   * @return 0
   * 	If \p x and \p y are not equal.
   */
  int TINYSPLINE_API
  ts_knots_equal(tsReal x,
                 tsReal y);
  
  /**
   * Fills the given array \p arr with \p val.
   *
   * @param[in] arr
   * 	The array to be filled.
   * @param[in] num
   * 	Fill length.
   * @param[in] val
   * 	The value to fill into \p arr.
   */
  void TINYSPLINE_API
  ts_arr_fill(tsReal *arr,
              size_t num,
              tsReal val);
  
  /**
   * Returns the euclidean distance of the points \p x and \p y.
   *
   * @param[in] x
   * 	First point.
   * @param[in] y
   * 	Second point.
   * @param[in] dim
   * 	Dimensionality of \p x and \p y.
   * @return
   * 	The euclidean distance of the points \p x and \p y.
   */
  tsReal TINYSPLINE_API
  ts_distance(const tsReal *x,
              const tsReal *y,
              size_t dim);
  /*! @} */
  
  
  
  #ifdef	__cplusplus
  }
  #endif
  
  #endif	/* TINYSPLINE_H */
  
  