From 24ca892afb02c1a548f4c8d5a687658614474f15 Mon Sep 17 00:00:00 2001 From: Marek Roszko Date: Tue, 6 Dec 2022 22:28:09 -0500 Subject: [PATCH] Update tinysplice to latest A few years worth of bug fixes --- pcbnew/import_gfx/dxf_import_plugin.cpp | 6 +- thirdparty/tinyspline_lib/CMakeLists.txt | 3 +- .../tinyspline_lib/{LICENSE.MIT => LICENSE} | 6 +- thirdparty/tinyspline_lib/parson.c | 2079 ++++++++ thirdparty/tinyspline_lib/parson.h | 234 + thirdparty/tinyspline_lib/tinyspline.c | 4494 ++++++++++++----- thirdparty/tinyspline_lib/tinyspline.h | 3645 ++++++++++--- thirdparty/tinyspline_lib/tinysplinecpp.cpp | 493 -- thirdparty/tinyspline_lib/tinysplinecpp.h | 143 - thirdparty/tinyspline_lib/tinysplinecxx.cxx | 1707 +++++++ thirdparty/tinyspline_lib/tinysplinecxx.h | 960 ++++ 11 files changed, 11207 insertions(+), 2563 deletions(-) rename thirdparty/tinyspline_lib/{LICENSE.MIT => LICENSE} (94%) create mode 100644 thirdparty/tinyspline_lib/parson.c create mode 100644 thirdparty/tinyspline_lib/parson.h delete mode 100644 thirdparty/tinyspline_lib/tinysplinecpp.cpp delete mode 100644 thirdparty/tinyspline_lib/tinysplinecpp.h create mode 100644 thirdparty/tinyspline_lib/tinysplinecxx.cxx create mode 100644 thirdparty/tinyspline_lib/tinysplinecxx.h diff --git a/pcbnew/import_gfx/dxf_import_plugin.cpp b/pcbnew/import_gfx/dxf_import_plugin.cpp index e0c6f4a9b3..a254d30d06 100644 --- a/pcbnew/import_gfx/dxf_import_plugin.cpp +++ b/pcbnew/import_gfx/dxf_import_plugin.cpp @@ -1316,7 +1316,7 @@ void DXF_IMPORT_PLUGIN::insertArc( const VECTOR2D& aSegStart, const VECTOR2D& aS } -#include "tinysplinecpp.h" +#include "tinysplinecxx.h" void DXF_IMPORT_PLUGIN::insertSpline( double aWidth ) { @@ -1367,11 +1367,11 @@ void DXF_IMPORT_PLUGIN::insertSpline( double aWidth ) tinyspline::BSpline dxfspline( m_curr_entity.m_SplineControlPointList.size(), /* coord dim */ 2, m_curr_entity.m_SplineDegree ); - dxfspline.setCtrlp( ctrlp ); + dxfspline.setControlPoints( ctrlp ); dxfspline.setKnots( m_curr_entity.m_SplineKnotsList ); tinyspline::BSpline beziers( dxfspline.toBeziers() ); - coords = beziers.ctrlp(); + coords = beziers.controlPoints(); } catch( const std::runtime_error& ) //tinyspline throws everything including data validation as runtime errors { diff --git a/thirdparty/tinyspline_lib/CMakeLists.txt b/thirdparty/tinyspline_lib/CMakeLists.txt index 606455ac4c..55029520cc 100644 --- a/thirdparty/tinyspline_lib/CMakeLists.txt +++ b/thirdparty/tinyspline_lib/CMakeLists.txt @@ -1,6 +1,7 @@ set(TINYSPLINE_LIB_SRCS tinyspline.c - tinysplinecpp.cpp + tinysplinecxx.cxx + parson.c ) add_library(tinyspline_lib STATIC ${TINYSPLINE_LIB_SRCS}) diff --git a/thirdparty/tinyspline_lib/LICENSE.MIT b/thirdparty/tinyspline_lib/LICENSE similarity index 94% rename from thirdparty/tinyspline_lib/LICENSE.MIT rename to thirdparty/tinyspline_lib/LICENSE index 7215f6950b..49834e53fa 100644 --- a/thirdparty/tinyspline_lib/LICENSE.MIT +++ b/thirdparty/tinyspline_lib/LICENSE @@ -1,3 +1,7 @@ +MIT License + +Copyright (c) 2016 Marcel Steinbeck + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights @@ -14,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/thirdparty/tinyspline_lib/parson.c b/thirdparty/tinyspline_lib/parson.c new file mode 100644 index 0000000000..7fac17c597 --- /dev/null +++ b/thirdparty/tinyspline_lib/parson.c @@ -0,0 +1,2079 @@ +/* + Parson ( http://kgabis.github.com/parson/ ) + Copyright (c) 2012 - 2017 Krzysztof Gabis + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +#ifdef _MSC_VER +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif /* _CRT_SECURE_NO_WARNINGS */ +#endif /* _MSC_VER */ + +#include "parson.h" + +#include +#include +#include +#include +#include +#include + +/* Suppress some useless MSVC warnings. */ +#ifdef _MSC_VER +#pragma warning(push) +/* address of dllimport */ +#pragma warning(disable:4232) +/* function not inlined */ +#pragma warning(disable:4710) +/* byte padding */ +#pragma warning(disable:4820) +/* meaningless deprecation */ +#pragma warning(disable:4996) +/* Spectre mitigation */ +#pragma warning(disable:5045) +#endif + +/* Apparently sscanf is not implemented in some "standard" libraries, so don't use it, if you + * don't have to. */ +#define sscanf THINK_TWICE_ABOUT_USING_SSCANF + +#define STARTING_CAPACITY 16 +#define MAX_NESTING 2048 + +#define FLOAT_FORMAT "%1.17g" /* do not increase precision without incresing NUM_BUF_SIZE */ +#define NUM_BUF_SIZE 64 /* double printed with "%1.17g" shouldn't be longer than 25 bytes so let's be paranoid and use 64 */ + +#define SIZEOF_TOKEN(a) (sizeof(a) - 1) +#define SKIP_CHAR(str) ((*str)++) +#define SKIP_WHITESPACES(str) while (isspace((unsigned char)(**str))) { SKIP_CHAR(str); } +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +#undef malloc +#undef free + +#if defined(isnan) && defined(isinf) +#define IS_NUMBER_INVALID(x) (isnan((x)) || isinf((x))) +#else +#define IS_NUMBER_INVALID(x) (((x) * 0.0) != 0.0) +#endif + +static JSON_Malloc_Function parson_malloc = malloc; +static JSON_Free_Function parson_free = free; + +#define IS_CONT(b) (((unsigned char)(b) & 0xC0) == 0x80) /* is utf-8 continuation byte */ + +/* Type definitions */ +typedef union json_value_value { + char *string; + double number; + JSON_Object *object; + JSON_Array *array; + int boolean; + int null; +} JSON_Value_Value; + +struct json_value_t { + JSON_Value *parent; + JSON_Value_Type type; + JSON_Value_Value value; +}; + +struct json_object_t { + JSON_Value *wrapping_value; + char **names; + JSON_Value **values; + size_t count; + size_t capacity; +}; + +struct json_array_t { + JSON_Value *wrapping_value; + JSON_Value **items; + size_t count; + size_t capacity; +}; + +/* Various */ +static char * read_file(const char *filename); +static void remove_comments(char *string, const char *start_token, const char *end_token); +static char * parson_strndup(const char *string, size_t n); +static char * parson_strdup(const char *string); +static int hex_char_to_int(char c); +static int parse_utf16_hex(const char *string, unsigned int *result); +static int num_bytes_in_utf8_sequence(unsigned char c); +static int verify_utf8_sequence(const unsigned char *string, int *len); +static int is_valid_utf8(const char *string, size_t string_len); +static int is_decimal(const char *string, size_t length); + +/* JSON Object */ +static JSON_Object * json_object_init(JSON_Value *wrapping_value); +static JSON_Status json_object_add(JSON_Object *object, const char *name, JSON_Value *value); +static JSON_Status json_object_addn(JSON_Object *object, const char *name, size_t name_len, JSON_Value *value); +static JSON_Status json_object_resize(JSON_Object *object, size_t new_capacity); +static JSON_Value * json_object_getn_value(const JSON_Object *object, const char *name, size_t name_len); +static JSON_Status json_object_remove_internal(JSON_Object *object, const char *name, int free_value); +static JSON_Status json_object_dotremove_internal(JSON_Object *object, const char *name, int free_value); +static void json_object_free(JSON_Object *object); + +/* JSON Array */ +static JSON_Array * json_array_init(JSON_Value *wrapping_value); +static JSON_Status json_array_add(JSON_Array *array, JSON_Value *value); +static JSON_Status json_array_resize(JSON_Array *array, size_t new_capacity); +static void json_array_free(JSON_Array *array); + +/* JSON Value */ +static JSON_Value * json_value_init_string_no_copy(char *string); + +/* Parser */ +static JSON_Status skip_quotes(const char **string); +static int parse_utf16(const char **unprocessed, char **processed); +static char * process_string(const char *input, size_t len); +static char * get_quoted_string(const char **string); +static JSON_Value * parse_object_value(const char **string, size_t nesting); +static JSON_Value * parse_array_value(const char **string, size_t nesting); +static JSON_Value * parse_string_value(const char **string); +static JSON_Value * parse_boolean_value(const char **string); +static JSON_Value * parse_number_value(const char **string); +static JSON_Value * parse_null_value(const char **string); +static JSON_Value * parse_value(const char **string, size_t nesting); + +/* Serialization */ +static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, int is_pretty, char *num_buf); +static int json_serialize_string(const char *string, char *buf); +static int append_indent(char *buf, int level); +static int append_string(char *buf, const char *string); + +/* Various */ +static char * parson_strndup(const char *string, size_t n) { + char *output_string = (char*)parson_malloc(n + 1); + if (!output_string) { + return NULL; + } + output_string[n] = '\0'; +#if __GNUC__ >= 8 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-truncation" +#endif + strncpy(output_string, string, n); +#if __GNUC__ >= 8 +#pragma GCC diagnostic pop +#endif + return output_string; +} + +static char * parson_strdup(const char *string) { + return parson_strndup(string, strlen(string)); +} + +static int hex_char_to_int(char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } else if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } + return -1; +} + +static int parse_utf16_hex(const char *s, unsigned int *result) { + int x1, x2, x3, x4; + if (s[0] == '\0' || s[1] == '\0' || s[2] == '\0' || s[3] == '\0') { + return 0; + } + x1 = hex_char_to_int(s[0]); + x2 = hex_char_to_int(s[1]); + x3 = hex_char_to_int(s[2]); + x4 = hex_char_to_int(s[3]); + if (x1 == -1 || x2 == -1 || x3 == -1 || x4 == -1) { + return 0; + } + *result = (unsigned int)((x1 << 12) | (x2 << 8) | (x3 << 4) | x4); + return 1; +} + +static int num_bytes_in_utf8_sequence(unsigned char c) { + if (c == 0xC0 || c == 0xC1 || c > 0xF4 || IS_CONT(c)) { + return 0; + } else if ((c & 0x80) == 0) { /* 0xxxxxxx */ + return 1; + } else if ((c & 0xE0) == 0xC0) { /* 110xxxxx */ + return 2; + } else if ((c & 0xF0) == 0xE0) { /* 1110xxxx */ + return 3; + } else if ((c & 0xF8) == 0xF0) { /* 11110xxx */ + return 4; + } + return 0; /* won't happen */ +} + +static int verify_utf8_sequence(const unsigned char *string, int *len) { + unsigned int cp = 0; + *len = num_bytes_in_utf8_sequence(string[0]); + + if (*len == 1) { + cp = string[0]; + } else if (*len == 2 && IS_CONT(string[1])) { + cp = string[0] & 0x1F; + cp = (cp << 6) | (string[1] & 0x3F); + } else if (*len == 3 && IS_CONT(string[1]) && IS_CONT(string[2])) { + cp = ((unsigned char)string[0]) & 0xF; + cp = (cp << 6) | (string[1] & 0x3F); + cp = (cp << 6) | (string[2] & 0x3F); + } else if (*len == 4 && IS_CONT(string[1]) && IS_CONT(string[2]) && IS_CONT(string[3])) { + cp = string[0] & 0x7; + cp = (cp << 6) | (string[1] & 0x3F); + cp = (cp << 6) | (string[2] & 0x3F); + cp = (cp << 6) | (string[3] & 0x3F); + } else { + return 0; + } + + /* overlong encodings */ + if ((cp < 0x80 && *len > 1) || + (cp < 0x800 && *len > 2) || + (cp < 0x10000 && *len > 3)) { + return 0; + } + + /* invalid unicode */ + if (cp > 0x10FFFF) { + return 0; + } + + /* surrogate halves */ + if (cp >= 0xD800 && cp <= 0xDFFF) { + return 0; + } + + return 1; +} + +static int is_valid_utf8(const char *string, size_t string_len) { + int len = 0; + const char *string_end = string + string_len; + while (string < string_end) { + if (!verify_utf8_sequence((const unsigned char*)string, &len)) { + return 0; + } + string += len; + } + return 1; +} + +static int is_decimal(const char *string, size_t length) { + if (length > 1 && string[0] == '0' && string[1] != '.') { + return 0; + } + if (length > 2 && !strncmp(string, "-0", 2) && string[2] != '.') { + return 0; + } + while (length--) { + if (strchr("xX", string[length])) { + return 0; + } + } + return 1; +} + +static char * read_file(const char * filename) { + FILE *fp = fopen(filename, "r"); + size_t size_to_read = 0; + size_t size_read = 0; + long pos; + char *file_contents; + if (!fp) { + return NULL; + } + fseek(fp, 0L, SEEK_END); + pos = ftell(fp); + if (pos < 0) { + fclose(fp); + return NULL; + } + size_to_read = pos; + rewind(fp); + file_contents = (char*)parson_malloc(sizeof(char) * (size_to_read + 1)); + if (!file_contents) { + fclose(fp); + return NULL; + } + size_read = fread(file_contents, 1, size_to_read, fp); + if (size_read == 0 || ferror(fp)) { + fclose(fp); + parson_free(file_contents); + return NULL; + } + fclose(fp); + file_contents[size_read] = '\0'; + return file_contents; +} + +static void remove_comments(char *string, const char *start_token, const char *end_token) { + int in_string = 0, escaped = 0; + size_t i; + char *ptr = NULL, current_char; + size_t start_token_len = strlen(start_token); + size_t end_token_len = strlen(end_token); + if (start_token_len == 0 || end_token_len == 0) { + return; + } + while ((current_char = *string) != '\0') { + if (current_char == '\\' && !escaped) { + escaped = 1; + string++; + continue; + } else if (current_char == '\"' && !escaped) { + in_string = !in_string; + } else if (!in_string && strncmp(string, start_token, start_token_len) == 0) { + for(i = 0; i < start_token_len; i++) { + string[i] = ' '; + } + string = string + start_token_len; + ptr = strstr(string, end_token); + if (!ptr) { + return; + } + for (i = 0; i < (ptr - string) + end_token_len; i++) { + string[i] = ' '; + } + string = ptr + end_token_len - 1; + } + escaped = 0; + string++; + } +} + +/* JSON Object */ +static JSON_Object * json_object_init(JSON_Value *wrapping_value) { + JSON_Object *new_obj = (JSON_Object*)parson_malloc(sizeof(JSON_Object)); + if (new_obj == NULL) { + return NULL; + } + new_obj->wrapping_value = wrapping_value; + new_obj->names = (char**)NULL; + new_obj->values = (JSON_Value**)NULL; + new_obj->capacity = 0; + new_obj->count = 0; + return new_obj; +} + +static JSON_Status json_object_add(JSON_Object *object, const char *name, JSON_Value *value) { + if (name == NULL) { + return JSONFailure; + } + return json_object_addn(object, name, strlen(name), value); +} + +static JSON_Status json_object_addn(JSON_Object *object, const char *name, size_t name_len, JSON_Value *value) { + size_t index = 0; + if (object == NULL || name == NULL || value == NULL) { + return JSONFailure; + } + if (json_object_getn_value(object, name, name_len) != NULL) { + return JSONFailure; + } + if (object->count >= object->capacity) { + size_t new_capacity = MAX(object->capacity * 2, STARTING_CAPACITY); + if (json_object_resize(object, new_capacity) == JSONFailure) { + return JSONFailure; + } + } + index = object->count; + object->names[index] = parson_strndup(name, name_len); + if (object->names[index] == NULL) { + return JSONFailure; + } + value->parent = json_object_get_wrapping_value(object); + object->values[index] = value; + object->count++; + return JSONSuccess; +} + +static JSON_Status json_object_resize(JSON_Object *object, size_t new_capacity) { + char **temp_names = NULL; + JSON_Value **temp_values = NULL; + + if ((object->names == NULL && object->values != NULL) || + (object->names != NULL && object->values == NULL) || + new_capacity == 0) { + return JSONFailure; /* Shouldn't happen */ + } + temp_names = (char**)parson_malloc(new_capacity * sizeof(char*)); + if (temp_names == NULL) { + return JSONFailure; + } + temp_values = (JSON_Value**)parson_malloc(new_capacity * sizeof(JSON_Value*)); + if (temp_values == NULL) { + parson_free(temp_names); + return JSONFailure; + } + if (object->names != NULL && object->values != NULL && object->count > 0) { + memcpy(temp_names, object->names, object->count * sizeof(char*)); + memcpy(temp_values, object->values, object->count * sizeof(JSON_Value*)); + } + parson_free(object->names); + parson_free(object->values); + object->names = temp_names; + object->values = temp_values; + object->capacity = new_capacity; + return JSONSuccess; +} + +static JSON_Value * json_object_getn_value(const JSON_Object *object, const char *name, size_t name_len) { + size_t i, name_length; + for (i = 0; i < json_object_get_count(object); i++) { + name_length = strlen(object->names[i]); + if (name_length != name_len) { + continue; + } + if (strncmp(object->names[i], name, name_len) == 0) { + return object->values[i]; + } + } + return NULL; +} + +static JSON_Status json_object_remove_internal(JSON_Object *object, const char *name, int free_value) { + size_t i = 0, last_item_index = 0; + if (object == NULL || json_object_get_value(object, name) == NULL) { + return JSONFailure; + } + last_item_index = json_object_get_count(object) - 1; + for (i = 0; i < json_object_get_count(object); i++) { + if (strcmp(object->names[i], name) == 0) { + parson_free(object->names[i]); + if (free_value) { + json_value_free(object->values[i]); + } + if (i != last_item_index) { /* Replace key value pair with one from the end */ + object->names[i] = object->names[last_item_index]; + object->values[i] = object->values[last_item_index]; + } + object->count -= 1; + return JSONSuccess; + } + } + return JSONFailure; /* No execution path should end here */ +} + +static JSON_Status json_object_dotremove_internal(JSON_Object *object, const char *name, int free_value) { + JSON_Value *temp_value = NULL; + JSON_Object *temp_object = NULL; + const char *dot_pos = strchr(name, '.'); + if (dot_pos == NULL) { + return json_object_remove_internal(object, name, free_value); + } + temp_value = json_object_getn_value(object, name, dot_pos - name); + if (json_value_get_type(temp_value) != JSONObject) { + return JSONFailure; + } + temp_object = json_value_get_object(temp_value); + return json_object_dotremove_internal(temp_object, dot_pos + 1, free_value); +} + +static void json_object_free(JSON_Object *object) { + size_t i; + for (i = 0; i < object->count; i++) { + parson_free(object->names[i]); + json_value_free(object->values[i]); + } + parson_free(object->names); + parson_free(object->values); + parson_free(object); +} + +/* JSON Array */ +static JSON_Array * json_array_init(JSON_Value *wrapping_value) { + JSON_Array *new_array = (JSON_Array*)parson_malloc(sizeof(JSON_Array)); + if (new_array == NULL) { + return NULL; + } + new_array->wrapping_value = wrapping_value; + new_array->items = (JSON_Value**)NULL; + new_array->capacity = 0; + new_array->count = 0; + return new_array; +} + +static JSON_Status json_array_add(JSON_Array *array, JSON_Value *value) { + if (array->count >= array->capacity) { + size_t new_capacity = MAX(array->capacity * 2, STARTING_CAPACITY); + if (json_array_resize(array, new_capacity) == JSONFailure) { + return JSONFailure; + } + } + value->parent = json_array_get_wrapping_value(array); + array->items[array->count] = value; + array->count++; + return JSONSuccess; +} + +static JSON_Status json_array_resize(JSON_Array *array, size_t new_capacity) { + JSON_Value **new_items = NULL; + if (new_capacity == 0) { + return JSONFailure; + } + new_items = (JSON_Value**)parson_malloc(new_capacity * sizeof(JSON_Value*)); + if (new_items == NULL) { + return JSONFailure; + } + if (array->items != NULL && array->count > 0) { + memcpy(new_items, array->items, array->count * sizeof(JSON_Value*)); + } + parson_free(array->items); + array->items = new_items; + array->capacity = new_capacity; + return JSONSuccess; +} + +static void json_array_free(JSON_Array *array) { + size_t i; + for (i = 0; i < array->count; i++) { + json_value_free(array->items[i]); + } + parson_free(array->items); + parson_free(array); +} + +/* JSON Value */ +static JSON_Value * json_value_init_string_no_copy(char *string) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) { + return NULL; + } + new_value->parent = NULL; + new_value->type = JSONString; + new_value->value.string = string; + return new_value; +} + +/* Parser */ +static JSON_Status skip_quotes(const char **string) { + if (**string != '\"') { + return JSONFailure; + } + SKIP_CHAR(string); + while (**string != '\"') { + if (**string == '\0') { + return JSONFailure; + } else if (**string == '\\') { + SKIP_CHAR(string); + if (**string == '\0') { + return JSONFailure; + } + } + SKIP_CHAR(string); + } + SKIP_CHAR(string); + return JSONSuccess; +} + +static int parse_utf16(const char **unprocessed, char **processed) { + unsigned int cp, lead, trail; + int parse_succeeded = 0; + char *processed_ptr = *processed; + const char *unprocessed_ptr = *unprocessed; + unprocessed_ptr++; /* skips u */ + parse_succeeded = parse_utf16_hex(unprocessed_ptr, &cp); + if (!parse_succeeded) { + return JSONFailure; + } + if (cp < 0x80) { + processed_ptr[0] = (char)cp; /* 0xxxxxxx */ + } else if (cp < 0x800) { + processed_ptr[0] = ((cp >> 6) & 0x1F) | 0xC0; /* 110xxxxx */ + processed_ptr[1] = ((cp) & 0x3F) | 0x80; /* 10xxxxxx */ + processed_ptr += 1; + } else if (cp < 0xD800 || cp > 0xDFFF) { + processed_ptr[0] = ((cp >> 12) & 0x0F) | 0xE0; /* 1110xxxx */ + processed_ptr[1] = ((cp >> 6) & 0x3F) | 0x80; /* 10xxxxxx */ + processed_ptr[2] = ((cp) & 0x3F) | 0x80; /* 10xxxxxx */ + processed_ptr += 2; + } else if (cp >= 0xD800 && cp <= 0xDBFF) { /* lead surrogate (0xD800..0xDBFF) */ + lead = cp; + unprocessed_ptr += 4; /* should always be within the buffer, otherwise previous sscanf would fail */ + if (*unprocessed_ptr++ != '\\' || *unprocessed_ptr++ != 'u') { + return JSONFailure; + } + parse_succeeded = parse_utf16_hex(unprocessed_ptr, &trail); + if (!parse_succeeded || trail < 0xDC00 || trail > 0xDFFF) { /* valid trail surrogate? (0xDC00..0xDFFF) */ + return JSONFailure; + } + cp = ((((lead - 0xD800) & 0x3FF) << 10) | ((trail - 0xDC00) & 0x3FF)) + 0x010000; + processed_ptr[0] = (((cp >> 18) & 0x07) | 0xF0); /* 11110xxx */ + processed_ptr[1] = (((cp >> 12) & 0x3F) | 0x80); /* 10xxxxxx */ + processed_ptr[2] = (((cp >> 6) & 0x3F) | 0x80); /* 10xxxxxx */ + processed_ptr[3] = (((cp) & 0x3F) | 0x80); /* 10xxxxxx */ + processed_ptr += 3; + } else { /* trail surrogate before lead surrogate */ + return JSONFailure; + } + unprocessed_ptr += 3; + *processed = processed_ptr; + *unprocessed = unprocessed_ptr; + return JSONSuccess; +} + + +/* Copies and processes passed string up to supplied length. +Example: "\u006Corem ipsum" -> lorem ipsum */ +static char* process_string(const char *input, size_t len) { + const char *input_ptr = input; + size_t initial_size = (len + 1) * sizeof(char); + size_t final_size = 0; + char *output = NULL, *output_ptr = NULL, *resized_output = NULL; + output = (char*)parson_malloc(initial_size); + if (output == NULL) { + goto error; + } + output_ptr = output; + while ((*input_ptr != '\0') && (size_t)(input_ptr - input) < len) { + if (*input_ptr == '\\') { + input_ptr++; + switch (*input_ptr) { + case '\"': *output_ptr = '\"'; break; + case '\\': *output_ptr = '\\'; break; + case '/': *output_ptr = '/'; break; + case 'b': *output_ptr = '\b'; break; + case 'f': *output_ptr = '\f'; break; + case 'n': *output_ptr = '\n'; break; + case 'r': *output_ptr = '\r'; break; + case 't': *output_ptr = '\t'; break; + case 'u': + if (parse_utf16(&input_ptr, &output_ptr) == JSONFailure) { + goto error; + } + break; + default: + goto error; + } + } else if ((unsigned char)*input_ptr < 0x20) { + goto error; /* 0x00-0x19 are invalid characters for json string (http://www.ietf.org/rfc/rfc4627.txt) */ + } else { + *output_ptr = *input_ptr; + } + output_ptr++; + input_ptr++; + } + *output_ptr = '\0'; + /* resize to new length */ + final_size = (size_t)(output_ptr-output) + 1; + /* todo: don't resize if final_size == initial_size */ + resized_output = (char*)parson_malloc(final_size); + if (resized_output == NULL) { + goto error; + } + memcpy(resized_output, output, final_size); + parson_free(output); + return resized_output; +error: + parson_free(output); + return NULL; +} + +/* Return processed contents of a string between quotes and + skips passed argument to a matching quote. */ +static char * get_quoted_string(const char **string) { + const char *string_start = *string; + size_t string_len = 0; + JSON_Status status = skip_quotes(string); + if (status != JSONSuccess) { + return NULL; + } + string_len = *string - string_start - 2; /* length without quotes */ + return process_string(string_start + 1, string_len); +} + +static JSON_Value * parse_value(const char **string, size_t nesting) { + if (nesting > MAX_NESTING) { + return NULL; + } + SKIP_WHITESPACES(string); + switch (**string) { + case '{': + return parse_object_value(string, nesting + 1); + case '[': + return parse_array_value(string, nesting + 1); + case '\"': + return parse_string_value(string); + case 'f': case 't': + return parse_boolean_value(string); + case '-': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + return parse_number_value(string); + case 'n': + return parse_null_value(string); + default: + return NULL; + } +} + +static JSON_Value * parse_object_value(const char **string, size_t nesting) { + JSON_Value *output_value = NULL, *new_value = NULL; + JSON_Object *output_object = NULL; + char *new_key = NULL; + output_value = json_value_init_object(); + if (output_value == NULL) { + return NULL; + } + if (**string != '{') { + json_value_free(output_value); + return NULL; + } + output_object = json_value_get_object(output_value); + SKIP_CHAR(string); + SKIP_WHITESPACES(string); + if (**string == '}') { /* empty object */ + SKIP_CHAR(string); + return output_value; + } + while (**string != '\0') { + new_key = get_quoted_string(string); + if (new_key == NULL) { + json_value_free(output_value); + return NULL; + } + SKIP_WHITESPACES(string); + if (**string != ':') { + parson_free(new_key); + json_value_free(output_value); + return NULL; + } + SKIP_CHAR(string); + new_value = parse_value(string, nesting); + if (new_value == NULL) { + parson_free(new_key); + json_value_free(output_value); + return NULL; + } + if (json_object_add(output_object, new_key, new_value) == JSONFailure) { + parson_free(new_key); + json_value_free(new_value); + json_value_free(output_value); + return NULL; + } + parson_free(new_key); + SKIP_WHITESPACES(string); + if (**string != ',') { + break; + } + SKIP_CHAR(string); + SKIP_WHITESPACES(string); + } + SKIP_WHITESPACES(string); + if (**string != '}' || /* Trim object after parsing is over */ + json_object_resize(output_object, json_object_get_count(output_object)) == JSONFailure) { + json_value_free(output_value); + return NULL; + } + SKIP_CHAR(string); + return output_value; +} + +static JSON_Value * parse_array_value(const char **string, size_t nesting) { + JSON_Value *output_value = NULL, *new_array_value = NULL; + JSON_Array *output_array = NULL; + output_value = json_value_init_array(); + if (output_value == NULL) { + return NULL; + } + if (**string != '[') { + json_value_free(output_value); + return NULL; + } + output_array = json_value_get_array(output_value); + SKIP_CHAR(string); + SKIP_WHITESPACES(string); + if (**string == ']') { /* empty array */ + SKIP_CHAR(string); + return output_value; + } + while (**string != '\0') { + new_array_value = parse_value(string, nesting); + if (new_array_value == NULL) { + json_value_free(output_value); + return NULL; + } + if (json_array_add(output_array, new_array_value) == JSONFailure) { + json_value_free(new_array_value); + json_value_free(output_value); + return NULL; + } + SKIP_WHITESPACES(string); + if (**string != ',') { + break; + } + SKIP_CHAR(string); + SKIP_WHITESPACES(string); + } + SKIP_WHITESPACES(string); + if (**string != ']' || /* Trim array after parsing is over */ + json_array_resize(output_array, json_array_get_count(output_array)) == JSONFailure) { + json_value_free(output_value); + return NULL; + } + SKIP_CHAR(string); + return output_value; +} + +static JSON_Value * parse_string_value(const char **string) { + JSON_Value *value = NULL; + char *new_string = get_quoted_string(string); + if (new_string == NULL) { + return NULL; + } + value = json_value_init_string_no_copy(new_string); + if (value == NULL) { + parson_free(new_string); + return NULL; + } + return value; +} + +static JSON_Value * parse_boolean_value(const char **string) { + size_t true_token_size = SIZEOF_TOKEN("true"); + size_t false_token_size = SIZEOF_TOKEN("false"); + if (strncmp("true", *string, true_token_size) == 0) { + *string += true_token_size; + return json_value_init_boolean(1); + } else if (strncmp("false", *string, false_token_size) == 0) { + *string += false_token_size; + return json_value_init_boolean(0); + } + return NULL; +} + +static JSON_Value * parse_number_value(const char **string) { + char *end; + double number = 0; + errno = 0; + number = strtod(*string, &end); + if (errno || !is_decimal(*string, end - *string)) { + return NULL; + } + *string = end; + return json_value_init_number(number); +} + +static JSON_Value * parse_null_value(const char **string) { + size_t token_size = SIZEOF_TOKEN("null"); + if (strncmp("null", *string, token_size) == 0) { + *string += token_size; + return json_value_init_null(); + } + return NULL; +} + +/* Serialization */ +#define APPEND_STRING(str) do { written = append_string(buf, (str));\ + if (written < 0) { return -1; }\ + if (buf != NULL) { buf += written; }\ + written_total += written; } while(0) + +#define APPEND_INDENT(level) do { written = append_indent(buf, (level));\ + if (written < 0) { return -1; }\ + if (buf != NULL) { buf += written; }\ + written_total += written; } while(0) + +static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, int is_pretty, char *num_buf) +{ + const char *key = NULL, *string = NULL; + JSON_Value *temp_value = NULL; + JSON_Array *array = NULL; + JSON_Object *object = NULL; + size_t i = 0, count = 0; + double num = 0.0; + int written = -1, written_total = 0; + + switch (json_value_get_type(value)) { + case JSONArray: + array = json_value_get_array(value); + count = json_array_get_count(array); + APPEND_STRING("["); + if (count > 0 && is_pretty) { + APPEND_STRING("\n"); + } + for (i = 0; i < count; i++) { + if (is_pretty) { + APPEND_INDENT(level+1); + } + temp_value = json_array_get_value(array, i); + written = json_serialize_to_buffer_r(temp_value, buf, level+1, is_pretty, num_buf); + if (written < 0) { + return -1; + } + if (buf != NULL) { + buf += written; + } + written_total += written; + if (i < (count - 1)) { + APPEND_STRING(","); + } + if (is_pretty) { + APPEND_STRING("\n"); + } + } + if (count > 0 && is_pretty) { + APPEND_INDENT(level); + } + APPEND_STRING("]"); + return written_total; + case JSONObject: + object = json_value_get_object(value); + count = json_object_get_count(object); + APPEND_STRING("{"); + if (count > 0 && is_pretty) { + APPEND_STRING("\n"); + } + for (i = 0; i < count; i++) { + key = json_object_get_name(object, i); + if (key == NULL) { + return -1; + } + if (is_pretty) { + APPEND_INDENT(level+1); + } + written = json_serialize_string(key, buf); + if (written < 0) { + return -1; + } + if (buf != NULL) { + buf += written; + } + written_total += written; + APPEND_STRING(":"); + if (is_pretty) { + APPEND_STRING(" "); + } + temp_value = json_object_get_value(object, key); + written = json_serialize_to_buffer_r(temp_value, buf, level+1, is_pretty, num_buf); + if (written < 0) { + return -1; + } + if (buf != NULL) { + buf += written; + } + written_total += written; + if (i < (count - 1)) { + APPEND_STRING(","); + } + if (is_pretty) { + APPEND_STRING("\n"); + } + } + if (count > 0 && is_pretty) { + APPEND_INDENT(level); + } + APPEND_STRING("}"); + return written_total; + case JSONString: + string = json_value_get_string(value); + if (string == NULL) { + return -1; + } + written = json_serialize_string(string, buf); + if (written < 0) { + return -1; + } + if (buf != NULL) { + buf += written; + } + written_total += written; + return written_total; + case JSONBoolean: + if (json_value_get_boolean(value)) { + APPEND_STRING("true"); + } else { + APPEND_STRING("false"); + } + return written_total; + case JSONNumber: + num = json_value_get_number(value); + if (buf != NULL) { + num_buf = buf; + } + written = sprintf(num_buf, FLOAT_FORMAT, num); + if (written < 0) { + return -1; + } + if (buf != NULL) { + buf += written; + } + written_total += written; + return written_total; + case JSONNull: + APPEND_STRING("null"); + return written_total; + case JSONError: + return -1; + default: + return -1; + } +} + +static int json_serialize_string(const char *string, char *buf) { + size_t i = 0, len = strlen(string); + char c = '\0'; + int written = -1, written_total = 0; + APPEND_STRING("\""); + for (i = 0; i < len; i++) { + c = string[i]; + switch (c) { + case '\"': APPEND_STRING("\\\""); break; + case '\\': APPEND_STRING("\\\\"); break; + case '/': APPEND_STRING("\\/"); break; /* to make json embeddable in xml\/html */ + case '\b': APPEND_STRING("\\b"); break; + case '\f': APPEND_STRING("\\f"); break; + case '\n': APPEND_STRING("\\n"); break; + case '\r': APPEND_STRING("\\r"); break; + case '\t': APPEND_STRING("\\t"); break; + case '\x00': APPEND_STRING("\\u0000"); break; + case '\x01': APPEND_STRING("\\u0001"); break; + case '\x02': APPEND_STRING("\\u0002"); break; + case '\x03': APPEND_STRING("\\u0003"); break; + case '\x04': APPEND_STRING("\\u0004"); break; + case '\x05': APPEND_STRING("\\u0005"); break; + case '\x06': APPEND_STRING("\\u0006"); break; + case '\x07': APPEND_STRING("\\u0007"); break; + /* '\x08' duplicate: '\b' */ + /* '\x09' duplicate: '\t' */ + /* '\x0a' duplicate: '\n' */ + case '\x0b': APPEND_STRING("\\u000b"); break; + /* '\x0c' duplicate: '\f' */ + /* '\x0d' duplicate: '\r' */ + case '\x0e': APPEND_STRING("\\u000e"); break; + case '\x0f': APPEND_STRING("\\u000f"); break; + case '\x10': APPEND_STRING("\\u0010"); break; + case '\x11': APPEND_STRING("\\u0011"); break; + case '\x12': APPEND_STRING("\\u0012"); break; + case '\x13': APPEND_STRING("\\u0013"); break; + case '\x14': APPEND_STRING("\\u0014"); break; + case '\x15': APPEND_STRING("\\u0015"); break; + case '\x16': APPEND_STRING("\\u0016"); break; + case '\x17': APPEND_STRING("\\u0017"); break; + case '\x18': APPEND_STRING("\\u0018"); break; + case '\x19': APPEND_STRING("\\u0019"); break; + case '\x1a': APPEND_STRING("\\u001a"); break; + case '\x1b': APPEND_STRING("\\u001b"); break; + case '\x1c': APPEND_STRING("\\u001c"); break; + case '\x1d': APPEND_STRING("\\u001d"); break; + case '\x1e': APPEND_STRING("\\u001e"); break; + case '\x1f': APPEND_STRING("\\u001f"); break; + default: + if (buf != NULL) { + buf[0] = c; + buf += 1; + } + written_total += 1; + break; + } + } + APPEND_STRING("\""); + return written_total; +} + +static int append_indent(char *buf, int level) { + int i; + int written = -1, written_total = 0; + for (i = 0; i < level; i++) { + APPEND_STRING(" "); + } + return written_total; +} + +static int append_string(char *buf, const char *string) { + if (buf == NULL) { + return (int)strlen(string); + } + return sprintf(buf, "%s", string); +} + +#undef APPEND_STRING +#undef APPEND_INDENT + +/* Parser API */ +JSON_Value * json_parse_file(const char *filename) { + char *file_contents = read_file(filename); + JSON_Value *output_value = NULL; + if (file_contents == NULL) { + return NULL; + } + output_value = json_parse_string(file_contents); + parson_free(file_contents); + return output_value; +} + +JSON_Value * json_parse_file_with_comments(const char *filename) { + char *file_contents = read_file(filename); + JSON_Value *output_value = NULL; + if (file_contents == NULL) { + return NULL; + } + output_value = json_parse_string_with_comments(file_contents); + parson_free(file_contents); + return output_value; +} + +JSON_Value * json_parse_string(const char *string) { + if (string == NULL) { + return NULL; + } + if (string[0] == '\xEF' && string[1] == '\xBB' && string[2] == '\xBF') { + string = string + 3; /* Support for UTF-8 BOM */ + } + return parse_value((const char**)&string, 0); +} + +JSON_Value * json_parse_string_with_comments(const char *string) { + JSON_Value *result = NULL; + char *string_mutable_copy = NULL, *string_mutable_copy_ptr = NULL; + string_mutable_copy = parson_strdup(string); + if (string_mutable_copy == NULL) { + return NULL; + } + remove_comments(string_mutable_copy, "/*", "*/"); + remove_comments(string_mutable_copy, "//", "\n"); + string_mutable_copy_ptr = string_mutable_copy; + result = parse_value((const char**)&string_mutable_copy_ptr, 0); + parson_free(string_mutable_copy); + return result; +} + +/* JSON Object API */ + +JSON_Value * json_object_get_value(const JSON_Object *object, const char *name) { + if (object == NULL || name == NULL) { + return NULL; + } + return json_object_getn_value(object, name, strlen(name)); +} + +const char * json_object_get_string(const JSON_Object *object, const char *name) { + return json_value_get_string(json_object_get_value(object, name)); +} + +double json_object_get_number(const JSON_Object *object, const char *name) { + return json_value_get_number(json_object_get_value(object, name)); +} + +JSON_Object * json_object_get_object(const JSON_Object *object, const char *name) { + return json_value_get_object(json_object_get_value(object, name)); +} + +JSON_Array * json_object_get_array(const JSON_Object *object, const char *name) { + return json_value_get_array(json_object_get_value(object, name)); +} + +int json_object_get_boolean(const JSON_Object *object, const char *name) { + return json_value_get_boolean(json_object_get_value(object, name)); +} + +JSON_Value * json_object_dotget_value(const JSON_Object *object, const char *name) { + const char *dot_position = strchr(name, '.'); + if (!dot_position) { + return json_object_get_value(object, name); + } + object = json_value_get_object(json_object_getn_value(object, name, dot_position - name)); + return json_object_dotget_value(object, dot_position + 1); +} + +const char * json_object_dotget_string(const JSON_Object *object, const char *name) { + return json_value_get_string(json_object_dotget_value(object, name)); +} + +double json_object_dotget_number(const JSON_Object *object, const char *name) { + return json_value_get_number(json_object_dotget_value(object, name)); +} + +JSON_Object * json_object_dotget_object(const JSON_Object *object, const char *name) { + return json_value_get_object(json_object_dotget_value(object, name)); +} + +JSON_Array * json_object_dotget_array(const JSON_Object *object, const char *name) { + return json_value_get_array(json_object_dotget_value(object, name)); +} + +int json_object_dotget_boolean(const JSON_Object *object, const char *name) { + return json_value_get_boolean(json_object_dotget_value(object, name)); +} + +size_t json_object_get_count(const JSON_Object *object) { + return object ? object->count : 0; +} + +const char * json_object_get_name(const JSON_Object *object, size_t index) { + if (object == NULL || index >= json_object_get_count(object)) { + return NULL; + } + return object->names[index]; +} + +JSON_Value * json_object_get_value_at(const JSON_Object *object, size_t index) { + if (object == NULL || index >= json_object_get_count(object)) { + return NULL; + } + return object->values[index]; +} + +JSON_Value *json_object_get_wrapping_value(const JSON_Object *object) { + return object->wrapping_value; +} + +int json_object_has_value (const JSON_Object *object, const char *name) { + return json_object_get_value(object, name) != NULL; +} + +int json_object_has_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type) { + JSON_Value *val = json_object_get_value(object, name); + return val != NULL && json_value_get_type(val) == type; +} + +int json_object_dothas_value (const JSON_Object *object, const char *name) { + return json_object_dotget_value(object, name) != NULL; +} + +int json_object_dothas_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type) { + JSON_Value *val = json_object_dotget_value(object, name); + return val != NULL && json_value_get_type(val) == type; +} + +/* JSON Array API */ +JSON_Value * json_array_get_value(const JSON_Array *array, size_t index) { + if (array == NULL || index >= json_array_get_count(array)) { + return NULL; + } + return array->items[index]; +} + +const char * json_array_get_string(const JSON_Array *array, size_t index) { + return json_value_get_string(json_array_get_value(array, index)); +} + +double json_array_get_number(const JSON_Array *array, size_t index) { + return json_value_get_number(json_array_get_value(array, index)); +} + +JSON_Object * json_array_get_object(const JSON_Array *array, size_t index) { + return json_value_get_object(json_array_get_value(array, index)); +} + +JSON_Array * json_array_get_array(const JSON_Array *array, size_t index) { + return json_value_get_array(json_array_get_value(array, index)); +} + +int json_array_get_boolean(const JSON_Array *array, size_t index) { + return json_value_get_boolean(json_array_get_value(array, index)); +} + +size_t json_array_get_count(const JSON_Array *array) { + return array ? array->count : 0; +} + +JSON_Value * json_array_get_wrapping_value(const JSON_Array *array) { + return array->wrapping_value; +} + +/* JSON Value API */ +JSON_Value_Type json_value_get_type(const JSON_Value *value) { + return value ? value->type : JSONError; +} + +JSON_Object * json_value_get_object(const JSON_Value *value) { + return json_value_get_type(value) == JSONObject ? value->value.object : NULL; +} + +JSON_Array * json_value_get_array(const JSON_Value *value) { + return json_value_get_type(value) == JSONArray ? value->value.array : NULL; +} + +const char * json_value_get_string(const JSON_Value *value) { + return json_value_get_type(value) == JSONString ? value->value.string : NULL; +} + +double json_value_get_number(const JSON_Value *value) { + return json_value_get_type(value) == JSONNumber ? value->value.number : 0; +} + +int json_value_get_boolean(const JSON_Value *value) { + return json_value_get_type(value) == JSONBoolean ? value->value.boolean : -1; +} + +JSON_Value * json_value_get_parent (const JSON_Value *value) { + return value ? value->parent : NULL; +} + +void json_value_free(JSON_Value *value) { + switch (json_value_get_type(value)) { + case JSONObject: + json_object_free(value->value.object); + break; + case JSONString: + parson_free(value->value.string); + break; + case JSONArray: + json_array_free(value->value.array); + break; + default: + break; + } + parson_free(value); +} + +JSON_Value * json_value_init_object(void) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) { + return NULL; + } + new_value->parent = NULL; + new_value->type = JSONObject; + new_value->value.object = json_object_init(new_value); + if (!new_value->value.object) { + parson_free(new_value); + return NULL; + } + return new_value; +} + +JSON_Value * json_value_init_array(void) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) { + return NULL; + } + new_value->parent = NULL; + new_value->type = JSONArray; + new_value->value.array = json_array_init(new_value); + if (!new_value->value.array) { + parson_free(new_value); + return NULL; + } + return new_value; +} + +JSON_Value * json_value_init_string(const char *string) { + char *copy = NULL; + JSON_Value *value; + size_t string_len = 0; + if (string == NULL) { + return NULL; + } + string_len = strlen(string); + if (!is_valid_utf8(string, string_len)) { + return NULL; + } + copy = parson_strndup(string, string_len); + if (copy == NULL) { + return NULL; + } + value = json_value_init_string_no_copy(copy); + if (value == NULL) { + parson_free(copy); + } + return value; +} + +JSON_Value * json_value_init_number(double number) { + JSON_Value *new_value = NULL; +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + if (IS_NUMBER_INVALID(number)) { +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + return NULL; + } + new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (new_value == NULL) { + return NULL; + } + new_value->parent = NULL; + new_value->type = JSONNumber; + new_value->value.number = number; + return new_value; +} + +JSON_Value * json_value_init_boolean(int boolean) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) { + return NULL; + } + new_value->parent = NULL; + new_value->type = JSONBoolean; + new_value->value.boolean = boolean ? 1 : 0; + return new_value; +} + +JSON_Value * json_value_init_null(void) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) { + return NULL; + } + new_value->parent = NULL; + new_value->type = JSONNull; + return new_value; +} + +JSON_Value * json_value_deep_copy(const JSON_Value *value) { + size_t i = 0; + JSON_Value *return_value = NULL, *temp_value_copy = NULL, *temp_value = NULL; + const char *temp_string = NULL, *temp_key = NULL; + char *temp_string_copy = NULL; + JSON_Array *temp_array = NULL, *temp_array_copy = NULL; + JSON_Object *temp_object = NULL, *temp_object_copy = NULL; + + switch (json_value_get_type(value)) { + case JSONArray: + temp_array = json_value_get_array(value); + return_value = json_value_init_array(); + if (return_value == NULL) { + return NULL; + } + temp_array_copy = json_value_get_array(return_value); + for (i = 0; i < json_array_get_count(temp_array); i++) { + temp_value = json_array_get_value(temp_array, i); + temp_value_copy = json_value_deep_copy(temp_value); + if (temp_value_copy == NULL) { + json_value_free(return_value); + return NULL; + } + if (json_array_add(temp_array_copy, temp_value_copy) == JSONFailure) { + json_value_free(return_value); + json_value_free(temp_value_copy); + return NULL; + } + } + return return_value; + case JSONObject: + temp_object = json_value_get_object(value); + return_value = json_value_init_object(); + if (return_value == NULL) { + return NULL; + } + temp_object_copy = json_value_get_object(return_value); + for (i = 0; i < json_object_get_count(temp_object); i++) { + temp_key = json_object_get_name(temp_object, i); + temp_value = json_object_get_value(temp_object, temp_key); + temp_value_copy = json_value_deep_copy(temp_value); + if (temp_value_copy == NULL) { + json_value_free(return_value); + return NULL; + } + if (json_object_add(temp_object_copy, temp_key, temp_value_copy) == JSONFailure) { + json_value_free(return_value); + json_value_free(temp_value_copy); + return NULL; + } + } + return return_value; + case JSONBoolean: + return json_value_init_boolean(json_value_get_boolean(value)); + case JSONNumber: + return json_value_init_number(json_value_get_number(value)); + case JSONString: + temp_string = json_value_get_string(value); + if (temp_string == NULL) { + return NULL; + } + temp_string_copy = parson_strdup(temp_string); + if (temp_string_copy == NULL) { + return NULL; + } + return_value = json_value_init_string_no_copy(temp_string_copy); + if (return_value == NULL) { + parson_free(temp_string_copy); + } + return return_value; + case JSONNull: + return json_value_init_null(); + case JSONError: + return NULL; + default: + return NULL; + } +} + +size_t json_serialization_size(const JSON_Value *value) { + char num_buf[NUM_BUF_SIZE]; /* recursively allocating buffer on stack is a bad idea, so let's do it only once */ + int res = json_serialize_to_buffer_r(value, NULL, 0, 0, num_buf); + return res < 0 ? 0 : (size_t)(res + 1); +} + +JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes) { + int written = -1; + size_t needed_size_in_bytes = json_serialization_size(value); + if (needed_size_in_bytes == 0 || buf_size_in_bytes < needed_size_in_bytes) { + return JSONFailure; + } + written = json_serialize_to_buffer_r(value, buf, 0, 0, NULL); + if (written < 0) { + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename) { + JSON_Status return_code = JSONSuccess; + FILE *fp = NULL; + char *serialized_string = json_serialize_to_string(value); + if (serialized_string == NULL) { + return JSONFailure; + } + fp = fopen(filename, "w"); + if (fp == NULL) { + json_free_serialized_string(serialized_string); + return JSONFailure; + } + if (fputs(serialized_string, fp) == EOF) { + return_code = JSONFailure; + } + if (fclose(fp) == EOF) { + return_code = JSONFailure; + } + json_free_serialized_string(serialized_string); + return return_code; +} + +char * json_serialize_to_string(const JSON_Value *value) { + JSON_Status serialization_result = JSONFailure; + size_t buf_size_bytes = json_serialization_size(value); + char *buf = NULL; + if (buf_size_bytes == 0) { + return NULL; + } + buf = (char*)parson_malloc(buf_size_bytes); + if (buf == NULL) { + return NULL; + } + serialization_result = json_serialize_to_buffer(value, buf, buf_size_bytes); + if (serialization_result == JSONFailure) { + json_free_serialized_string(buf); + return NULL; + } + return buf; +} + +size_t json_serialization_size_pretty(const JSON_Value *value) { + char num_buf[NUM_BUF_SIZE]; /* recursively allocating buffer on stack is a bad idea, so let's do it only once */ + int res = json_serialize_to_buffer_r(value, NULL, 0, 1, num_buf); + return res < 0 ? 0 : (size_t)(res + 1); +} + +JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, size_t buf_size_in_bytes) { + int written = -1; + size_t needed_size_in_bytes = json_serialization_size_pretty(value); + if (needed_size_in_bytes == 0 || buf_size_in_bytes < needed_size_in_bytes) { + return JSONFailure; + } + written = json_serialize_to_buffer_r(value, buf, 0, 1, NULL); + if (written < 0) { + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_serialize_to_file_pretty(const JSON_Value *value, const char *filename) { + JSON_Status return_code = JSONSuccess; + FILE *fp = NULL; + char *serialized_string = json_serialize_to_string_pretty(value); + if (serialized_string == NULL) { + return JSONFailure; + } + fp = fopen(filename, "w"); + if (fp == NULL) { + json_free_serialized_string(serialized_string); + return JSONFailure; + } + if (fputs(serialized_string, fp) == EOF) { + return_code = JSONFailure; + } + if (fclose(fp) == EOF) { + return_code = JSONFailure; + } + json_free_serialized_string(serialized_string); + return return_code; +} + +char * json_serialize_to_string_pretty(const JSON_Value *value) { + JSON_Status serialization_result = JSONFailure; + size_t buf_size_bytes = json_serialization_size_pretty(value); + char *buf = NULL; + if (buf_size_bytes == 0) { + return NULL; + } + buf = (char*)parson_malloc(buf_size_bytes); + if (buf == NULL) { + return NULL; + } + serialization_result = json_serialize_to_buffer_pretty(value, buf, buf_size_bytes); + if (serialization_result == JSONFailure) { + json_free_serialized_string(buf); + return NULL; + } + return buf; +} + +void json_free_serialized_string(char *string) { + parson_free(string); +} + +JSON_Status json_array_remove(JSON_Array *array, size_t ix) { + size_t to_move_bytes = 0; + if (array == NULL || ix >= json_array_get_count(array)) { + return JSONFailure; + } + json_value_free(json_array_get_value(array, ix)); + to_move_bytes = (json_array_get_count(array) - 1 - ix) * sizeof(JSON_Value*); + memmove(array->items + ix, array->items + ix + 1, to_move_bytes); + array->count -= 1; + return JSONSuccess; +} + +JSON_Status json_array_replace_value(JSON_Array *array, size_t ix, JSON_Value *value) { + if (array == NULL || value == NULL || value->parent != NULL || ix >= json_array_get_count(array)) { + return JSONFailure; + } + json_value_free(json_array_get_value(array, ix)); + value->parent = json_array_get_wrapping_value(array); + array->items[ix] = value; + return JSONSuccess; +} + +JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char* string) { + JSON_Value *value = json_value_init_string(string); + if (value == NULL) { + return JSONFailure; + } + if (json_array_replace_value(array, i, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number) { + JSON_Value *value = json_value_init_number(number); + if (value == NULL) { + return JSONFailure; + } + if (json_array_replace_value(array, i, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean) { + JSON_Value *value = json_value_init_boolean(boolean); + if (value == NULL) { + return JSONFailure; + } + if (json_array_replace_value(array, i, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_replace_null(JSON_Array *array, size_t i) { + JSON_Value *value = json_value_init_null(); + if (value == NULL) { + return JSONFailure; + } + if (json_array_replace_value(array, i, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_clear(JSON_Array *array) { + size_t i = 0; + if (array == NULL) { + return JSONFailure; + } + for (i = 0; i < json_array_get_count(array); i++) { + json_value_free(json_array_get_value(array, i)); + } + array->count = 0; + return JSONSuccess; +} + +JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value) { + if (array == NULL || value == NULL || value->parent != NULL) { + return JSONFailure; + } + return json_array_add(array, value); +} + +JSON_Status json_array_append_string(JSON_Array *array, const char *string) { + JSON_Value *value = json_value_init_string(string); + if (value == NULL) { + return JSONFailure; + } + if (json_array_append_value(array, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_append_number(JSON_Array *array, double number) { + JSON_Value *value = json_value_init_number(number); + if (value == NULL) { + return JSONFailure; + } + if (json_array_append_value(array, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_append_boolean(JSON_Array *array, int boolean) { + JSON_Value *value = json_value_init_boolean(boolean); + if (value == NULL) { + return JSONFailure; + } + if (json_array_append_value(array, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_append_null(JSON_Array *array) { + JSON_Value *value = json_value_init_null(); + if (value == NULL) { + return JSONFailure; + } + if (json_array_append_value(array, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value) { + size_t i = 0; + JSON_Value *old_value; + if (object == NULL || name == NULL || value == NULL || value->parent != NULL) { + return JSONFailure; + } + old_value = json_object_get_value(object, name); + if (old_value != NULL) { /* free and overwrite old value */ + json_value_free(old_value); + for (i = 0; i < json_object_get_count(object); i++) { + if (strcmp(object->names[i], name) == 0) { + value->parent = json_object_get_wrapping_value(object); + object->values[i] = value; + return JSONSuccess; + } + } + } + /* add new key value pair */ + return json_object_add(object, name, value); +} + +JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string) { + return json_object_set_value(object, name, json_value_init_string(string)); +} + +JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number) { + return json_object_set_value(object, name, json_value_init_number(number)); +} + +JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean) { + return json_object_set_value(object, name, json_value_init_boolean(boolean)); +} + +JSON_Status json_object_set_null(JSON_Object *object, const char *name) { + return json_object_set_value(object, name, json_value_init_null()); +} + +JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value) { + const char *dot_pos = NULL; + JSON_Value *temp_value = NULL, *new_value = NULL; + JSON_Object *temp_object = NULL, *new_object = NULL; + JSON_Status status = JSONFailure; + size_t name_len = 0; + if (object == NULL || name == NULL || value == NULL) { + return JSONFailure; + } + dot_pos = strchr(name, '.'); + if (dot_pos == NULL) { + return json_object_set_value(object, name, value); + } + name_len = dot_pos - name; + temp_value = json_object_getn_value(object, name, name_len); + if (temp_value) { + /* Don't overwrite existing non-object (unlike json_object_set_value, but it shouldn't be changed at this point) */ + if (json_value_get_type(temp_value) != JSONObject) { + return JSONFailure; + } + temp_object = json_value_get_object(temp_value); + return json_object_dotset_value(temp_object, dot_pos + 1, value); + } + new_value = json_value_init_object(); + if (new_value == NULL) { + return JSONFailure; + } + new_object = json_value_get_object(new_value); + status = json_object_dotset_value(new_object, dot_pos + 1, value); + if (status != JSONSuccess) { + json_value_free(new_value); + return JSONFailure; + } + status = json_object_addn(object, name, name_len, new_value); + if (status != JSONSuccess) { + json_object_dotremove_internal(new_object, dot_pos + 1, 0); + json_value_free(new_value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string) { + JSON_Value *value = json_value_init_string(string); + if (value == NULL) { + return JSONFailure; + } + if (json_object_dotset_value(object, name, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number) { + JSON_Value *value = json_value_init_number(number); + if (value == NULL) { + return JSONFailure; + } + if (json_object_dotset_value(object, name, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean) { + JSON_Value *value = json_value_init_boolean(boolean); + if (value == NULL) { + return JSONFailure; + } + if (json_object_dotset_value(object, name, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_object_dotset_null(JSON_Object *object, const char *name) { + JSON_Value *value = json_value_init_null(); + if (value == NULL) { + return JSONFailure; + } + if (json_object_dotset_value(object, name, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_object_remove(JSON_Object *object, const char *name) { + return json_object_remove_internal(object, name, 1); +} + +JSON_Status json_object_dotremove(JSON_Object *object, const char *name) { + return json_object_dotremove_internal(object, name, 1); +} + +JSON_Status json_object_clear(JSON_Object *object) { + size_t i = 0; + if (object == NULL) { + return JSONFailure; + } + for (i = 0; i < json_object_get_count(object); i++) { + parson_free(object->names[i]); + json_value_free(object->values[i]); + } + object->count = 0; + return JSONSuccess; +} + +JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value) { + JSON_Value *temp_schema_value = NULL, *temp_value = NULL; + JSON_Array *schema_array = NULL, *value_array = NULL; + JSON_Object *schema_object = NULL, *value_object = NULL; + JSON_Value_Type schema_type = JSONError, value_type = JSONError; + const char *key = NULL; + size_t i = 0, count = 0; + if (schema == NULL || value == NULL) { + return JSONFailure; + } + schema_type = json_value_get_type(schema); + value_type = json_value_get_type(value); + if (schema_type != value_type && schema_type != JSONNull) { /* null represents all values */ + return JSONFailure; + } + switch (schema_type) { + case JSONArray: + schema_array = json_value_get_array(schema); + value_array = json_value_get_array(value); + count = json_array_get_count(schema_array); + if (count == 0) { + return JSONSuccess; /* Empty array allows all types */ + } + /* Get first value from array, rest is ignored */ + temp_schema_value = json_array_get_value(schema_array, 0); + for (i = 0; i < json_array_get_count(value_array); i++) { + temp_value = json_array_get_value(value_array, i); + if (json_validate(temp_schema_value, temp_value) == JSONFailure) { + return JSONFailure; + } + } + return JSONSuccess; + case JSONObject: + schema_object = json_value_get_object(schema); + value_object = json_value_get_object(value); + count = json_object_get_count(schema_object); + if (count == 0) { + return JSONSuccess; /* Empty object allows all objects */ + } else if (json_object_get_count(value_object) < count) { + return JSONFailure; /* Tested object mustn't have less name-value pairs than schema */ + } + for (i = 0; i < count; i++) { + key = json_object_get_name(schema_object, i); + temp_schema_value = json_object_get_value(schema_object, key); + temp_value = json_object_get_value(value_object, key); + if (temp_value == NULL) { + return JSONFailure; + } + if (json_validate(temp_schema_value, temp_value) == JSONFailure) { + return JSONFailure; + } + } + return JSONSuccess; + case JSONString: case JSONNumber: case JSONBoolean: case JSONNull: + return JSONSuccess; /* equality already tested before switch */ + case JSONError: default: + return JSONFailure; + } +} + +int json_value_equals(const JSON_Value *a, const JSON_Value *b) { + JSON_Object *a_object = NULL, *b_object = NULL; + JSON_Array *a_array = NULL, *b_array = NULL; + const char *a_string = NULL, *b_string = NULL; + const char *key = NULL; + size_t a_count = 0, b_count = 0, i = 0; + JSON_Value_Type a_type, b_type; + a_type = json_value_get_type(a); + b_type = json_value_get_type(b); + if (a_type != b_type) { + return 0; + } + switch (a_type) { + case JSONArray: + a_array = json_value_get_array(a); + b_array = json_value_get_array(b); + a_count = json_array_get_count(a_array); + b_count = json_array_get_count(b_array); + if (a_count != b_count) { + return 0; + } + for (i = 0; i < a_count; i++) { + if (!json_value_equals(json_array_get_value(a_array, i), + json_array_get_value(b_array, i))) { + return 0; + } + } + return 1; + case JSONObject: + a_object = json_value_get_object(a); + b_object = json_value_get_object(b); + a_count = json_object_get_count(a_object); + b_count = json_object_get_count(b_object); + if (a_count != b_count) { + return 0; + } + for (i = 0; i < a_count; i++) { + key = json_object_get_name(a_object, i); + if (!json_value_equals(json_object_get_value(a_object, key), + json_object_get_value(b_object, key))) { + return 0; + } + } + return 1; + case JSONString: + a_string = json_value_get_string(a); + b_string = json_value_get_string(b); + if (a_string == NULL || b_string == NULL) { + return 0; /* shouldn't happen */ + } + return strcmp(a_string, b_string) == 0; + case JSONBoolean: + return json_value_get_boolean(a) == json_value_get_boolean(b); + case JSONNumber: + return fabs(json_value_get_number(a) - json_value_get_number(b)) < 0.000001; /* EPSILON */ + case JSONError: + return 1; + case JSONNull: + return 1; + default: + return 1; + } +} + +JSON_Value_Type json_type(const JSON_Value *value) { + return json_value_get_type(value); +} + +JSON_Object * json_object (const JSON_Value *value) { + return json_value_get_object(value); +} + +JSON_Array * json_array (const JSON_Value *value) { + return json_value_get_array(value); +} + +const char * json_string (const JSON_Value *value) { + return json_value_get_string(value); +} + +double json_number (const JSON_Value *value) { + return json_value_get_number(value); +} + +int json_boolean(const JSON_Value *value) { + return json_value_get_boolean(value); +} + +void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun) { + parson_malloc = malloc_fun; + parson_free = free_fun; +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/thirdparty/tinyspline_lib/parson.h b/thirdparty/tinyspline_lib/parson.h new file mode 100644 index 0000000000..6438c9367a --- /dev/null +++ b/thirdparty/tinyspline_lib/parson.h @@ -0,0 +1,234 @@ +/* + Parson ( http://kgabis.github.com/parson/ ) + Copyright (c) 2012 - 2017 Krzysztof Gabis + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef parson_parson_h +#define parson_parson_h + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include /* size_t */ + +/* Types and enums */ +typedef struct json_object_t JSON_Object; +typedef struct json_array_t JSON_Array; +typedef struct json_value_t JSON_Value; + +enum json_value_type { + JSONError = -1, + JSONNull = 1, + JSONString = 2, + JSONNumber = 3, + JSONObject = 4, + JSONArray = 5, + JSONBoolean = 6 +}; +typedef int JSON_Value_Type; + +enum json_result_t { + JSONSuccess = 0, + JSONFailure = -1 +}; +typedef int JSON_Status; + +typedef void * (*JSON_Malloc_Function)(size_t); +typedef void (*JSON_Free_Function)(void *); + +/* Call only once, before calling any other function from parson API. If not called, malloc and free + from stdlib will be used for all allocations */ +void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun); + +/* Parses first JSON value in a file, returns NULL in case of error */ +JSON_Value * json_parse_file(const char *filename); + +/* Parses first JSON value in a file and ignores comments (/ * * / and //), + returns NULL in case of error */ +JSON_Value * json_parse_file_with_comments(const char *filename); + +/* Parses first JSON value in a string, returns NULL in case of error */ +JSON_Value * json_parse_string(const char *string); + +/* Parses first JSON value in a string and ignores comments (/ * * / and //), + returns NULL in case of error */ +JSON_Value * json_parse_string_with_comments(const char *string); + +/* Serialization */ +size_t json_serialization_size(const JSON_Value *value); /* returns 0 on fail */ +JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes); +JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename); +char * json_serialize_to_string(const JSON_Value *value); + +/* Pretty serialization */ +size_t json_serialization_size_pretty(const JSON_Value *value); /* returns 0 on fail */ +JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, size_t buf_size_in_bytes); +JSON_Status json_serialize_to_file_pretty(const JSON_Value *value, const char *filename); +char * json_serialize_to_string_pretty(const JSON_Value *value); + +void json_free_serialized_string(char *string); /* frees string from json_serialize_to_string and json_serialize_to_string_pretty */ + +/* Comparing */ +int json_value_equals(const JSON_Value *a, const JSON_Value *b); + +/* Validation + This is *NOT* JSON Schema. It validates json by checking if object have identically + named fields with matching types. + For example schema {"name":"", "age":0} will validate + {"name":"Joe", "age":25} and {"name":"Joe", "age":25, "gender":"m"}, + but not {"name":"Joe"} or {"name":"Joe", "age":"Cucumber"}. + In case of arrays, only first value in schema is checked against all values in tested array. + Empty objects ({}) validate all objects, empty arrays ([]) validate all arrays, + null validates values of every type. + */ +JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value); + +/* + * JSON Object + */ +JSON_Value * json_object_get_value (const JSON_Object *object, const char *name); +const char * json_object_get_string (const JSON_Object *object, const char *name); +JSON_Object * json_object_get_object (const JSON_Object *object, const char *name); +JSON_Array * json_object_get_array (const JSON_Object *object, const char *name); +double json_object_get_number (const JSON_Object *object, const char *name); /* returns 0 on fail */ +int json_object_get_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */ + +/* dotget functions enable addressing values with dot notation in nested objects, + just like in structs or c++/java/c# objects (e.g. objectA.objectB.value). + Because valid names in JSON can contain dots, some values may be inaccessible + this way. */ +JSON_Value * json_object_dotget_value (const JSON_Object *object, const char *name); +const char * json_object_dotget_string (const JSON_Object *object, const char *name); +JSON_Object * json_object_dotget_object (const JSON_Object *object, const char *name); +JSON_Array * json_object_dotget_array (const JSON_Object *object, const char *name); +double json_object_dotget_number (const JSON_Object *object, const char *name); /* returns 0 on fail */ +int json_object_dotget_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */ + +/* Functions to get available names */ +size_t json_object_get_count (const JSON_Object *object); +const char * json_object_get_name (const JSON_Object *object, size_t index); +JSON_Value * json_object_get_value_at(const JSON_Object *object, size_t index); +JSON_Value * json_object_get_wrapping_value(const JSON_Object *object); + +/* Functions to check if object has a value with a specific name. Returned value is 1 if object has + * a value and 0 if it doesn't. dothas functions behave exactly like dotget functions. */ +int json_object_has_value (const JSON_Object *object, const char *name); +int json_object_has_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type); + +int json_object_dothas_value (const JSON_Object *object, const char *name); +int json_object_dothas_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type); + +/* Creates new name-value pair or frees and replaces old value with a new one. + * json_object_set_value does not copy passed value so it shouldn't be freed afterwards. */ +JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value); +JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string); +JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number); +JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean); +JSON_Status json_object_set_null(JSON_Object *object, const char *name); + +/* Works like dotget functions, but creates whole hierarchy if necessary. + * json_object_dotset_value does not copy passed value so it shouldn't be freed afterwards. */ +JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value); +JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string); +JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number); +JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean); +JSON_Status json_object_dotset_null(JSON_Object *object, const char *name); + +/* Frees and removes name-value pair */ +JSON_Status json_object_remove(JSON_Object *object, const char *name); + +/* Works like dotget function, but removes name-value pair only on exact match. */ +JSON_Status json_object_dotremove(JSON_Object *object, const char *key); + +/* Removes all name-value pairs in object */ +JSON_Status json_object_clear(JSON_Object *object); + +/* + *JSON Array + */ +JSON_Value * json_array_get_value (const JSON_Array *array, size_t index); +const char * json_array_get_string (const JSON_Array *array, size_t index); +JSON_Object * json_array_get_object (const JSON_Array *array, size_t index); +JSON_Array * json_array_get_array (const JSON_Array *array, size_t index); +double json_array_get_number (const JSON_Array *array, size_t index); /* returns 0 on fail */ +int json_array_get_boolean(const JSON_Array *array, size_t index); /* returns -1 on fail */ +size_t json_array_get_count (const JSON_Array *array); +JSON_Value * json_array_get_wrapping_value(const JSON_Array *array); + +/* Frees and removes value at given index, does nothing and returns JSONFailure if index doesn't exist. + * Order of values in array may change during execution. */ +JSON_Status json_array_remove(JSON_Array *array, size_t i); + +/* Frees and removes from array value at given index and replaces it with given one. + * Does nothing and returns JSONFailure if index doesn't exist. + * json_array_replace_value does not copy passed value so it shouldn't be freed afterwards. */ +JSON_Status json_array_replace_value(JSON_Array *array, size_t i, JSON_Value *value); +JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char* string); +JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number); +JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean); +JSON_Status json_array_replace_null(JSON_Array *array, size_t i); + +/* Frees and removes all values from array */ +JSON_Status json_array_clear(JSON_Array *array); + +/* Appends new value at the end of array. + * json_array_append_value does not copy passed value so it shouldn't be freed afterwards. */ +JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value); +JSON_Status json_array_append_string(JSON_Array *array, const char *string); +JSON_Status json_array_append_number(JSON_Array *array, double number); +JSON_Status json_array_append_boolean(JSON_Array *array, int boolean); +JSON_Status json_array_append_null(JSON_Array *array); + +/* + *JSON Value + */ +JSON_Value * json_value_init_object (void); +JSON_Value * json_value_init_array (void); +JSON_Value * json_value_init_string (const char *string); /* copies passed string */ +JSON_Value * json_value_init_number (double number); +JSON_Value * json_value_init_boolean(int boolean); +JSON_Value * json_value_init_null (void); +JSON_Value * json_value_deep_copy (const JSON_Value *value); +void json_value_free (JSON_Value *value); + +JSON_Value_Type json_value_get_type (const JSON_Value *value); +JSON_Object * json_value_get_object (const JSON_Value *value); +JSON_Array * json_value_get_array (const JSON_Value *value); +const char * json_value_get_string (const JSON_Value *value); +double json_value_get_number (const JSON_Value *value); +int json_value_get_boolean(const JSON_Value *value); +JSON_Value * json_value_get_parent (const JSON_Value *value); + +/* Same as above, but shorter */ +JSON_Value_Type json_type (const JSON_Value *value); +JSON_Object * json_object (const JSON_Value *value); +JSON_Array * json_array (const JSON_Value *value); +const char * json_string (const JSON_Value *value); +double json_number (const JSON_Value *value); +int json_boolean(const JSON_Value *value); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/thirdparty/tinyspline_lib/tinyspline.c b/thirdparty/tinyspline_lib/tinyspline.c index 1e62235f0c..dc27ee1f11 100644 --- a/thirdparty/tinyspline_lib/tinyspline.c +++ b/thirdparty/tinyspline_lib/tinyspline.c @@ -1,1389 +1,3481 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2016 Marcel Steinbeck - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - +#define TINYSPLINE_EXPORT #include "tinyspline.h" +#include "parson.h" /* serialization */ -#include /* malloc, free */ -#include /* fabs, sqrt */ -#include /* memcpy, memmove, strcmp */ -#include /* setjmp, longjmp */ +#include /* malloc, free */ +#include /* fabs, sqrt, acos */ +#include /* memcpy, memmove */ +#include /* FILE, fopen */ +#include /* varargs */ + +/* Suppress some useless MSVC warnings. */ +#ifdef _MSC_VER +#pragma warning(push) +/* address of dllimport */ +#pragma warning(disable:4232) +/* function not inlined */ +#pragma warning(disable:4710) +/* byte padding */ +#pragma warning(disable:4820) +/* meaningless deprecation */ +#pragma warning(disable:4996) +/* Spectre mitigation */ +#pragma warning(disable:5045) +#endif + +#define INIT_OUT_BSPLINE(in, out) \ + if ((in) != (out)) \ + ts_int_bspline_init(out); -/******************************************************** -* * -* Error handling * -* * -********************************************************/ -#define TRY( x, y ) y = (tsError) setjmp( x ); if( y == 0 ) { -#define CATCH } \ - else { -#define ETRY } - -/******************************************************** -* * -* Internal functions * -* * -********************************************************/ -void ts_internal_deboornet_copy( const tsDeBoorNet* original, - tsDeBoorNet* copy, jmp_buf buf - ) +/*! @name Internal Structs and Functions + * + * Internal functions are prefixed with \e ts_int (int for internal). + * + * @{ + */ +/** + * Stores the private data of ::tsBSpline. + */ +struct tsBSplineImpl { - const size_t dim = original->dim; - const size_t n_points = original->n_points; - const size_t sof_f = sizeof(tsReal); - const size_t sof_p = n_points * dim * sof_f; + size_t deg; /**< Degree of B-Spline basis function. */ + size_t dim; /**< Dimensionality of the control points (2D => x, y). */ + size_t n_ctrlp; /**< Number of control points. */ + size_t n_knots; /**< Number of knots (n_ctrlp + deg + 1). */ +}; - if( original == copy ) - return; +/** + * Stores the private data of ::tsDeBoorNet. + */ +struct tsDeBoorNetImpl +{ + tsReal u; /**< The evaluated knot. */ + size_t k; /**< The index [u_k, u_k+1) */ + size_t s; /**< Multiplicity of u_k. */ + size_t h; /**< Number of insertions required to obtain result. */ + size_t dim; /**< Dimensionality of the points (2D => x, y). */ + size_t n_points; /** Number of points in `points'. */ +}; - copy->u = original->u; - copy->k = original->k; - copy->s = original->s; - copy->h = original->h; - copy->dim = dim; - copy->n_points = n_points; - copy->points = (tsReal*) malloc( sof_p ); +void +ts_int_bspline_init(tsBSpline *spline) +{ + spline->pImpl = NULL; +} - if( copy->points == NULL ) - longjmp( buf, TS_MALLOC ); +size_t +ts_int_bspline_sof_state(const tsBSpline *spline) +{ + return sizeof(struct tsBSplineImpl) + + ts_bspline_sof_control_points(spline) + + ts_bspline_sof_knots(spline); +} - memcpy( copy->points, original->points, sof_p ); - copy->result = copy->points + (n_points - 1) * dim; +tsReal * +ts_int_bspline_access_ctrlp(const tsBSpline *spline) +{ + return (tsReal *) (& spline->pImpl[1]); +} + +tsReal * +ts_int_bspline_access_knots(const tsBSpline *spline) +{ + return ts_int_bspline_access_ctrlp(spline) + + ts_bspline_len_control_points(spline); +} + +tsError +ts_int_bspline_access_ctrlp_at(const tsBSpline *spline, + size_t index, + tsReal **ctrlp, + tsStatus *status) +{ + const size_t num = ts_bspline_num_control_points(spline); + if (index >= num) { + TS_RETURN_2(status, TS_INDEX_ERROR, + "index (%lu) >= num(control_points) (%lu)", + (unsigned long) index, + (unsigned long) num) + } + *ctrlp = ts_int_bspline_access_ctrlp(spline) + + index * ts_bspline_dimension(spline); + TS_RETURN_SUCCESS(status) +} + +tsError +ts_int_bspline_access_knot_at(const tsBSpline *spline, + size_t index, + tsReal *knot, + tsStatus *status) +{ + const size_t num = ts_bspline_num_knots(spline); + if (index >= num) { + TS_RETURN_2(status, TS_INDEX_ERROR, + "index (%lu) >= num(knots) (%lu)", + (unsigned long) index, + (unsigned long) num) + } + *knot = ts_int_bspline_access_knots(spline)[index]; + TS_RETURN_SUCCESS(status) +} + +void +ts_int_deboornet_init(tsDeBoorNet *net) +{ + net->pImpl = NULL; +} + +size_t +ts_int_deboornet_sof_state(const tsDeBoorNet *net) +{ + return sizeof(struct tsDeBoorNetImpl) + + ts_deboornet_sof_points(net) + + ts_deboornet_sof_result(net); +} + +tsReal * +ts_int_deboornet_access_points(const tsDeBoorNet *net) +{ + return (tsReal *) (& net->pImpl[1]); +} + +tsReal * +ts_int_deboornet_access_result(const tsDeBoorNet *net) +{ + if (ts_deboornet_num_result(net) == 2) { + return ts_int_deboornet_access_points(net); + } else { + return ts_int_deboornet_access_points(net) + + /* Last point in `points`. */ + (ts_deboornet_len_points(net) - + ts_deboornet_dimension(net)); + } +} +/*! @} */ + + + +/*! @name B-Spline Data + * + * @{ + */ +size_t +ts_bspline_degree(const tsBSpline *spline) +{ + return spline->pImpl->deg; +} + +size_t +ts_bspline_order(const tsBSpline *spline) +{ + return ts_bspline_degree(spline) + 1; +} + +size_t +ts_bspline_dimension(const tsBSpline *spline) +{ + return spline->pImpl->dim; +} + +size_t +ts_bspline_len_control_points(const tsBSpline *spline) +{ + return ts_bspline_num_control_points(spline) * + ts_bspline_dimension(spline); +} + +size_t +ts_bspline_num_control_points(const tsBSpline *spline) +{ + return spline->pImpl->n_ctrlp; +} + +size_t +ts_bspline_sof_control_points(const tsBSpline *spline) +{ + return ts_bspline_len_control_points(spline) * sizeof(tsReal); +} + +const tsReal * +ts_bspline_control_points_ptr(const tsBSpline *spline) +{ + return ts_int_bspline_access_ctrlp(spline); +} + +tsError +ts_bspline_control_points(const tsBSpline *spline, + tsReal **ctrlp, + tsStatus *status) +{ + const size_t size = ts_bspline_sof_control_points(spline); + *ctrlp = (tsReal*) malloc(size); + if (!*ctrlp) TS_RETURN_0(status, TS_MALLOC, "out of memory") + memcpy(*ctrlp, ts_int_bspline_access_ctrlp(spline), size); + TS_RETURN_SUCCESS(status) +} + +tsError +ts_bspline_control_point_at_ptr(const tsBSpline *spline, + size_t index, + const tsReal **ctrlp, + tsStatus *status) +{ + tsReal *vals; + tsError err; + TS_TRY(try, err, status) + TS_CALL(try, err, ts_int_bspline_access_ctrlp_at( + spline, index, &vals, status)) + *ctrlp = vals; + TS_CATCH(err) + *ctrlp = NULL; + TS_END_TRY_RETURN(err) +} + +tsError +ts_bspline_set_control_points(tsBSpline *spline, + const tsReal *ctrlp, + tsStatus *status) +{ + const size_t size = ts_bspline_sof_control_points(spline); + memmove(ts_int_bspline_access_ctrlp(spline), ctrlp, size); + TS_RETURN_SUCCESS(status) +} + +tsError +ts_bspline_set_control_point_at(tsBSpline *spline, + size_t index, + const tsReal *ctrlp, + tsStatus *status) +{ + tsReal *to; + size_t size; + tsError err; + TS_TRY(try, err, status) + TS_CALL(try, err, ts_int_bspline_access_ctrlp_at( + spline, index, &to, status)) + size = ts_bspline_dimension(spline) * sizeof(tsReal); + memcpy(to, ctrlp, size); + TS_END_TRY_RETURN(err) +} + +size_t +ts_bspline_num_knots(const tsBSpline *spline) +{ + return spline->pImpl->n_knots; +} + +size_t +ts_bspline_sof_knots(const tsBSpline *spline) +{ + return ts_bspline_num_knots(spline) * sizeof(tsReal); +} + +const tsReal * +ts_bspline_knots_ptr(const tsBSpline *spline) +{ + return ts_int_bspline_access_knots(spline); +} + +tsError +ts_bspline_knots(const tsBSpline *spline, + tsReal **knots, + tsStatus *status) +{ + const size_t size = ts_bspline_sof_knots(spline); + *knots = (tsReal*) malloc(size); + if (!*knots) TS_RETURN_0(status, TS_MALLOC, "out of memory") + memcpy(*knots, ts_int_bspline_access_knots(spline), size); + TS_RETURN_SUCCESS(status) +} + +tsError +ts_bspline_knot_at(const tsBSpline *spline, + size_t index, + tsReal *knot, + tsStatus *status) +{ + return ts_int_bspline_access_knot_at(spline, index, knot, status); +} + +tsError +ts_bspline_set_knots(tsBSpline *spline, + const tsReal *knots, + tsStatus *status) +{ + const size_t size = ts_bspline_sof_knots(spline); + const size_t num_knots = ts_bspline_num_knots(spline); + const size_t order = ts_bspline_order(spline); + size_t idx, mult; + tsReal lst_knot, knot; + lst_knot = knots[0]; + mult = 1; + for (idx = 1; idx < num_knots; idx++) { + knot = knots[idx]; + if (ts_knots_equal(lst_knot, knot)) { + mult++; + } else if (lst_knot > knot) { + TS_RETURN_1(status, TS_KNOTS_DECR, + "decreasing knot vector at index: %lu", + (unsigned long) idx) + } else { + mult = 0; + } + if (mult > order) { + TS_RETURN_3(status, TS_MULTIPLICITY, + "mult(%f) (%lu) > order (%lu)", + knot, (unsigned long) mult, + (unsigned long) order) + } + lst_knot = knot; + } + memmove(ts_int_bspline_access_knots(spline), knots, size); + TS_RETURN_SUCCESS(status) +} + +tsError +ts_bspline_set_knots_varargs(tsBSpline *spline, + tsStatus *status, + tsReal knot0, + double knot1, + ...) +{ + tsReal *values = NULL; + va_list argp; + size_t idx; + tsError err; + + TS_TRY(try, err, status) + TS_CALL(try, err, ts_bspline_knots( + spline, &values, status)) + + values[0] = knot0; + values[1] = (tsReal) knot1; + va_start(argp, knot1); + for (idx = 2; idx < ts_bspline_num_knots(spline); idx++) + values[idx] = (tsReal) va_arg(argp, double); + va_end(argp); + + TS_CALL(try, err, ts_bspline_set_knots( + spline, values, status)) + TS_FINALLY + if (values) free(values); + TS_END_TRY_RETURN(err) +} + +tsError +ts_bspline_set_knot_at(tsBSpline *spline, + size_t index, + tsReal knot, + tsStatus *status) +{ + tsError err; + tsReal *knots = NULL; + /* This is only for initialization. */ + tsReal oldKnot = ts_int_bspline_access_knots(spline)[0]; + TS_TRY(try, err, status) + TS_CALL(try, err, ts_int_bspline_access_knot_at( + spline, index, &oldKnot, status)) + /* knots must be set after reading oldKnot because the catch + * block assumes that oldKnot contains the correct value if + * knots is not NULL. */ + knots = ts_int_bspline_access_knots(spline); + knots[index] = knot; + TS_CALL(try, err, ts_bspline_set_knots( + spline, knots, status)) + TS_CATCH(err) + /* If knots is not NULL, oldKnot contains the correct value. */ + if (knots) knots[index] = oldKnot; + TS_END_TRY_RETURN(err) +} +/*! @} */ + + + +/*! @name B-Spline Initialization + * + * @{ + */ +tsBSpline +ts_bspline_init(void) +{ + tsBSpline spline; + ts_int_bspline_init(&spline); + return spline; +} + +tsError +ts_int_bspline_generate_knots(const tsBSpline *spline, + tsBSplineType type, + tsStatus *status) +{ + const size_t n_knots = ts_bspline_num_knots(spline); + const size_t deg = ts_bspline_degree(spline); + const size_t order = ts_bspline_order(spline); + tsReal fac; /**< Factor used to calculate the knot values. */ + size_t i; /**< Used in for loops. */ + tsReal *knots; /**< Pointer to the knots of \p _result_. */ + + /* order >= 1 implies 2*order >= 2 implies n_knots >= 2 */ + if (type == TS_BEZIERS && n_knots % order != 0) { + TS_RETURN_2(status, TS_NUM_KNOTS, + "num(knots) (%lu) %% order (%lu) != 0", + (unsigned long) n_knots, (unsigned long) order) + } + + knots = ts_int_bspline_access_knots(spline); + + if (type == TS_OPENED) { + knots[0] = TS_DOMAIN_DEFAULT_MIN; /* n_knots >= 2 */ + fac = (TS_DOMAIN_DEFAULT_MAX - TS_DOMAIN_DEFAULT_MIN) + / (n_knots - 1); /* n_knots >= 2 */ + for (i = 1; i < n_knots-1; i++) + knots[i] = TS_DOMAIN_DEFAULT_MIN + i*fac; + knots[i] = TS_DOMAIN_DEFAULT_MAX; /* n_knots >= 2 */ + } else if (type == TS_CLAMPED) { + /* n_knots >= 2*order == 2*(deg+1) == 2*deg + 2 > 2*deg - 1 */ + fac = (TS_DOMAIN_DEFAULT_MAX - TS_DOMAIN_DEFAULT_MIN) + / (n_knots - 2*deg - 1); + ts_arr_fill(knots, order, TS_DOMAIN_DEFAULT_MIN); + for (i = order ;i < n_knots-order; i++) + knots[i] = TS_DOMAIN_DEFAULT_MIN + (i-deg)*fac; + ts_arr_fill(knots + i, order, TS_DOMAIN_DEFAULT_MAX); + } else if (type == TS_BEZIERS) { + /* n_knots >= 2*order implies n_knots/order >= 2 */ + fac = (TS_DOMAIN_DEFAULT_MAX - TS_DOMAIN_DEFAULT_MIN) + / (n_knots/order - 1); + ts_arr_fill(knots, order, TS_DOMAIN_DEFAULT_MIN); + for (i = order; i < n_knots-order; i += order) + ts_arr_fill(knots + i, + order, + TS_DOMAIN_DEFAULT_MIN + (i/order)*fac); + ts_arr_fill(knots + i, order, TS_DOMAIN_DEFAULT_MAX); + } + TS_RETURN_SUCCESS(status) +} + +tsError +ts_bspline_new(size_t num_control_points, + size_t dimension, + size_t degree, + tsBSplineType type, + tsBSpline *spline, + tsStatus *status) +{ + const size_t order = degree + 1; + const size_t num_knots = num_control_points + order; + const size_t len_ctrlp = num_control_points * dimension; + + const size_t sof_real = sizeof(tsReal); + const size_t sof_impl = sizeof(struct tsBSplineImpl); + const size_t sof_ctrlp_vec = len_ctrlp * sof_real; + const size_t sof_knots_vec = num_knots * sof_real; + const size_t sof_spline = sof_impl + sof_ctrlp_vec + sof_knots_vec; + tsError err; + + ts_int_bspline_init(spline); + + if (dimension < 1) { + TS_RETURN_0(status, TS_DIM_ZERO, "unsupported dimension: 0") + } + if (num_knots > TS_MAX_NUM_KNOTS) { + TS_RETURN_2(status, TS_NUM_KNOTS, + "unsupported number of knots: %lu > %i", + (unsigned long) num_knots, TS_MAX_NUM_KNOTS) + } + if (degree >= num_control_points) { + TS_RETURN_2(status, TS_DEG_GE_NCTRLP, + "degree (%lu) >= num(control_points) (%lu)", + (unsigned long) degree, + (unsigned long) num_control_points) + } + + spline->pImpl = (struct tsBSplineImpl *) malloc(sof_spline); + if (!spline->pImpl) TS_RETURN_0(status, TS_MALLOC, "out of memory") + + spline->pImpl->deg = degree; + spline->pImpl->dim = dimension; + spline->pImpl->n_ctrlp = num_control_points; + spline->pImpl->n_knots = num_knots; + + TS_TRY(try, err, status) + TS_CALL(try, err, ts_int_bspline_generate_knots( + spline, type, status)) + TS_CATCH(err) + ts_bspline_free(spline); + TS_END_TRY_RETURN(err) +} + +tsError +ts_bspline_new_with_control_points(size_t num_control_points, + size_t dimension, + size_t degree, + tsBSplineType type, + tsBSpline *spline, + tsStatus *status, + double first, + ...) +{ + tsReal *ctrlp = NULL; + va_list argp; + size_t i; + tsError err; + + TS_TRY(try, err, status) + TS_CALL(try, err, ts_bspline_new( + num_control_points, dimension, + degree, type, spline, status)) + TS_CATCH(err) + ts_bspline_free(spline); + TS_END_TRY_ROE(err) + ctrlp = ts_int_bspline_access_ctrlp(spline); + + ctrlp[0] = (tsReal) first; + va_start(argp, first); + for (i = 1; i < ts_bspline_len_control_points(spline); i++) + ctrlp[i] = (tsReal) va_arg(argp, double); + va_end(argp); + + TS_RETURN_SUCCESS(status) +} + +tsError +ts_bspline_copy(const tsBSpline *src, + tsBSpline *dest, + tsStatus *status) +{ + size_t size; + if (src == dest) TS_RETURN_SUCCESS(status) + ts_int_bspline_init(dest); + size = ts_int_bspline_sof_state(src); + dest->pImpl = (struct tsBSplineImpl *) malloc(size); + if (!dest->pImpl) TS_RETURN_0(status, TS_MALLOC, "out of memory") + memcpy(dest->pImpl, src->pImpl, size); + TS_RETURN_SUCCESS(status) +} + +void +ts_bspline_move(tsBSpline *src, + tsBSpline *dest) +{ + if (src == dest) return; + dest->pImpl = src->pImpl; + ts_int_bspline_init(src); +} + +void +ts_bspline_free(tsBSpline *spline) +{ + if (spline->pImpl) free(spline->pImpl); + ts_int_bspline_init(spline); +} +/*! @} */ + + + +/*! @name De Boor Net Data + * + * @{ + */ +tsReal +ts_deboornet_knot(const tsDeBoorNet *net) +{ + return net->pImpl->u; +} + +size_t +ts_deboornet_index(const tsDeBoorNet *net) +{ + return net->pImpl->k; +} + +size_t +ts_deboornet_multiplicity(const tsDeBoorNet *net) +{ + return net->pImpl->s; +} + +size_t +ts_deboornet_num_insertions(const tsDeBoorNet *net) +{ + return net->pImpl->h; +} + +size_t +ts_deboornet_dimension(const tsDeBoorNet *net) +{ + return net->pImpl->dim; +} + +size_t +ts_deboornet_len_points(const tsDeBoorNet *net) +{ + return ts_deboornet_num_points(net) * + ts_deboornet_dimension(net); +} + +size_t +ts_deboornet_num_points(const tsDeBoorNet *net) +{ + return net->pImpl->n_points; +} + +size_t +ts_deboornet_sof_points(const tsDeBoorNet *net) +{ + return ts_deboornet_len_points(net) * sizeof(tsReal); +} + +const tsReal * +ts_deboornet_points_ptr(const tsDeBoorNet *net) +{ + return ts_int_deboornet_access_points(net); +} + +tsError +ts_deboornet_points(const tsDeBoorNet *net, + tsReal **points, + tsStatus *status) +{ + const size_t size = ts_deboornet_sof_points(net); + *points = (tsReal*) malloc(size); + if (!*points) TS_RETURN_0(status, TS_MALLOC, "out of memory") + memcpy(*points, ts_int_deboornet_access_points(net), size); + TS_RETURN_SUCCESS(status) +} + +size_t +ts_deboornet_len_result(const tsDeBoorNet *net) +{ + return ts_deboornet_num_result(net) * + ts_deboornet_dimension(net); +} + +size_t +ts_deboornet_num_result(const tsDeBoorNet *net) +{ + return ts_deboornet_num_points(net) == 2 ? 2 : 1; +} + +size_t +ts_deboornet_sof_result(const tsDeBoorNet *net) +{ + return ts_deboornet_len_result(net) * sizeof(tsReal); +} + +const tsReal * +ts_deboornet_result_ptr(const tsDeBoorNet *net) +{ + return ts_int_deboornet_access_result(net); +} + +tsError +ts_deboornet_result(const tsDeBoorNet *net, + tsReal **result, + tsStatus *status) +{ + const size_t size = ts_deboornet_sof_result(net); + *result = (tsReal*) malloc(size); + if (!*result) TS_RETURN_0(status, TS_MALLOC, "out of memory") + memcpy(*result, ts_int_deboornet_access_result(net), size); + TS_RETURN_SUCCESS(status) +} +/*! @} */ + + + +/*! @name De Boor Net Initialization + * + * @{ + */ +tsDeBoorNet +ts_deboornet_init(void) +{ + tsDeBoorNet net; + ts_int_deboornet_init(&net); + return net; +} + +tsError +ts_int_deboornet_new(const tsBSpline *spline, + tsDeBoorNet *net, + tsStatus *status) +{ + const size_t dim = ts_bspline_dimension(spline); + const size_t deg = ts_bspline_degree(spline); + const size_t order = ts_bspline_order(spline); + const size_t num_points = (size_t)(order * (order+1) * 0.5f); + /* Handle `order == 1' which generates too few points. */ + const size_t fixed_num_points = num_points < 2 ? 2 : num_points; + + const size_t sof_real = sizeof(tsReal); + const size_t sof_impl = sizeof(struct tsDeBoorNetImpl); + const size_t sof_points_vec = fixed_num_points * dim * sof_real; + const size_t sof_net = sof_impl * sof_points_vec; + + net->pImpl = (struct tsDeBoorNetImpl *) malloc(sof_net); + if (!net->pImpl) TS_RETURN_0(status, TS_MALLOC, "out of memory") + + net->pImpl->u = 0.f; + net->pImpl->k = 0; + net->pImpl->s = 0; + net->pImpl->h = deg; + net->pImpl->dim = dim; + net->pImpl->n_points = fixed_num_points; + TS_RETURN_SUCCESS(status) +} + +void +ts_deboornet_free(tsDeBoorNet *net) +{ + if (net->pImpl) free(net->pImpl); + ts_int_deboornet_init(net); +} + +tsError +ts_deboornet_copy(const tsDeBoorNet *src, + tsDeBoorNet *dest, + tsStatus *status) +{ + size_t size; + if (src == dest) TS_RETURN_SUCCESS(status) + ts_int_deboornet_init(dest); + size = ts_int_deboornet_sof_state(src); + dest->pImpl = (struct tsDeBoorNetImpl *) malloc(size); + if (!dest->pImpl) TS_RETURN_0(status, TS_MALLOC, "out of memory") + memcpy(dest->pImpl, src->pImpl, size); + TS_RETURN_SUCCESS(status) +} + +void +ts_deboornet_move(tsDeBoorNet *src, + tsDeBoorNet *dest) +{ + if (src == dest) return; + dest->pImpl = src->pImpl; + ts_int_deboornet_init(src); +} +/*! @} */ + + + +/*! @name Interpolation and Approximation Functions + * + * @{ + */ +tsError +ts_int_cubic_point(const tsReal *point, + size_t dim, + tsBSpline *spline, + tsStatus *status) +{ + const size_t size = dim * sizeof(tsReal); + tsReal *ctrlp = NULL; + size_t i; + tsError err; + TS_CALL_ROE(err, ts_bspline_new( + 4, dim, 3, + TS_CLAMPED, spline, status)) + ctrlp = ts_int_bspline_access_ctrlp(spline); + for (i = 0; i < 4; i++) { + memcpy(ctrlp + i*dim, + point, + size); + } + TS_RETURN_SUCCESS(status) +} + +tsError +ts_int_thomas_algorithm(const tsReal *a, + const tsReal *b, + const tsReal *c, + size_t num, + size_t dim, + tsReal *d, + tsStatus *status) +{ + size_t i, j, k, l; + tsReal m, *cc = NULL; + tsError err; + + if (dim == 0) { + TS_RETURN_0(status, TS_DIM_ZERO, + "unsupported dimension: 0") + } + if (num <= 1) { + TS_RETURN_1(status, TS_NUM_POINTS, + "num(points) (%lu) <= 1", + (unsigned long) num) + } + cc = (tsReal *) malloc(num * sizeof(tsReal)); + if (!cc) TS_RETURN_0(status, TS_MALLOC, "out of memory") + + TS_TRY(try, err, status) + /* Forward sweep. */ + if (fabs(b[0]) <= fabs(c[0])) { + TS_THROW_2(try, err, status, TS_NO_RESULT, + "error: |%f| <= |%f|", b[0], c[0]) + } + /* |b[i]| > |c[i]| implies that |b[i]| > 0. Thus, the following + * statements cannot evaluate to division by zero.*/ + cc[0] = c[0] / b[0]; + for (i = 0; i < dim; i++) + d[i] = d[i] / b[0]; + for (i = 1; i < num; i++) { + if (fabs(b[i]) <= fabs(a[i]) + fabs(c[i])) { + TS_THROW_3(try, err, status, TS_NO_RESULT, + "error: |%f| <= |%f| + |%f|", + b[i], a[i], c[i]) + } + /* |a[i]| < |b[i]| and cc[i - 1] < 1. Therefore, the + * following statement cannot evaluate to division by + * zero. */ + m = 1.f / (b[i] - a[i] * cc[i - 1]); + /* |b[i]| > |a[i]| + |c[i]| implies that there must be + * an eps > 0 such that |b[i]| = |a[i]| + |c[i]| + eps. + * Even if |a[i]| is 0 (by which the result of the + * following statement becomes maximum), |c[i]| is less + * than |b[i]| by an amount of eps. By substituting the + * previous and the following statements (under the + * assumption that |a[i]| is 0), we obtain c[i] / b[i], + * which must be less than 1. */ + cc[i] = c[i] * m; + for (j = 0; j < dim; j++) { + k = i * dim + j; + l = (i-1) * dim + j; + d[k] = (d[k] - a[i] * d[l]) * m; + } + } + + /* Back substitution. */ + for (i = num-1; i > 0; i--) { + for (j = 0; j < dim; j++) { + k = (i-1) * dim + j; + l = i * dim + j; + d[k] -= cc[i-1] * d[l]; + } + } + TS_FINALLY + free(cc); + TS_END_TRY_RETURN(err) +} + +tsError +ts_int_relaxed_uniform_cubic_bspline(const tsReal *points, + size_t n, + size_t dim, + tsBSpline *spline, + tsStatus *status) +{ + const size_t order = 4; /**< Order of spline to interpolate. */ + const tsReal as = 1.f/6.f; /**< The value 'a sixth'. */ + const tsReal at = 1.f/3.f; /**< The value 'a third'. */ + const tsReal tt = 2.f/3.f; /**< The value 'two third'. */ + size_t sof_ctrlp; /**< Size of a single control point. */ + const tsReal* b = points; /**< Array of the b values. */ + tsReal* s; /**< Array of the s values. */ + size_t i, d; /**< Used in for loops */ + size_t j, k, l; /**< Used as temporary indices. */ + tsReal *ctrlp; /**< Pointer to the control points of \p _spline_. */ + tsError err; + + /* input validation */ + if (dim == 0) + TS_RETURN_0(status, TS_DIM_ZERO, "unsupported dimension: 0") + if (n <= 1) { + TS_RETURN_1(status, TS_NUM_POINTS, + "num(points) (%lu) <= 1", + (unsigned long) n) + } + /* in the following n >= 2 applies */ + + sof_ctrlp = dim * sizeof(tsReal); /* dim > 0 implies sof_ctrlp > 0 */ + + s = NULL; + TS_TRY(try, err, status) + /* n >= 2 implies n-1 >= 1 implies (n-1)*4 >= 4 */ + TS_CALL(try, err, ts_bspline_new( + (n-1) * 4, dim, order - 1, + TS_BEZIERS, spline, status)) + ctrlp = ts_int_bspline_access_ctrlp(spline); + + s = (tsReal*) malloc(n * sof_ctrlp); + if (!s) { + TS_THROW_0(try, err, status, TS_MALLOC, + "out of memory") + } + + /* set s_0 to b_0 and s_n = b_n */ + memcpy(s, b, sof_ctrlp); + memcpy(s + (n-1)*dim, b + (n-1)*dim, sof_ctrlp); + + /* set s_i = 1/6*b_i + 2/3*b_{i-1} + 1/6*b_{i+1}*/ + for (i = 1; i < n-1; i++) { + for (d = 0; d < dim; d++) { + j = (i-1)*dim+d; + k = i*dim+d; + l = (i+1)*dim+d; + s[k] = as * b[j]; + s[k] += tt * b[k]; + s[k] += as * b[l]; + } + } + + /* create beziers from b and s */ + for (i = 0; i < n-1; i++) { + for (d = 0; d < dim; d++) { + j = i*dim+d; + k = i*4*dim+d; + l = (i+1)*dim+d; + ctrlp[k] = s[j]; + ctrlp[k+dim] = tt*b[j] + at*b[l]; + ctrlp[k+2*dim] = at*b[j] + tt*b[l]; + ctrlp[k+3*dim] = s[l]; + } + } + TS_CATCH(err) + ts_bspline_free(spline); + TS_FINALLY + if (s) + free(s); + TS_END_TRY_RETURN(err) +} + +tsError +ts_bspline_interpolate_cubic_natural(const tsReal *points, + size_t num_points, + size_t dimension, + tsBSpline *spline, + tsStatus *status) +{ + const size_t sof_ctrlp = dimension * sizeof(tsReal); + const size_t len_points = num_points * dimension; + const size_t num_int_points = num_points - 2; + const size_t len_int_points = num_int_points * dimension; + tsReal *thomas, *a, *b, *c, *d; + size_t i, j, k, l; + tsError err; + + ts_int_bspline_init(spline); + if (num_points == 0) + TS_RETURN_0(status, TS_NUM_POINTS, "num(points) == 0") + if (num_points == 1) { + TS_CALL_ROE(err, ts_int_cubic_point( + points, dimension, spline, status)) + TS_RETURN_SUCCESS(status) + } + if (num_points == 2) { + return ts_int_relaxed_uniform_cubic_bspline( + points, num_points, dimension, spline, status); + } + /* `num_points` >= 3 */ + thomas = NULL; + TS_TRY(try, err, status) + thomas = (tsReal *) malloc( + /* `a', `b', `c' (note that `c' is equal to `a') */ + 2 * num_int_points * sizeof(tsReal) + + /* `d' and "result of the thomas algorithm" (which + contains `num_points' points) */ + num_points * dimension * sizeof(tsReal)); + if (!thomas) { + TS_THROW_0(try, err, status, TS_MALLOC, + "out of memory") + } + /* The system of linear equations is taken from: + * http://www.bakoma-tex.com/doc/generic/pst-bspline/ + * pst-bspline-doc.pdf */ + a = c = thomas; + ts_arr_fill(a, num_int_points, 1); + b = a + num_int_points; + ts_arr_fill(b, num_int_points, 4); + d = b + num_int_points; + /* 6 * S_{i+1} */ + for (i = 0; i < num_int_points; i++) { + for (j = 0; j < dimension; j++) { + k = i * dimension + j; + l = (i+1) * dimension + j; + d[k] = 6 * points[l]; + } + } + for (i = 0; i < dimension; i++) { + /* 6 * S_{1} - S_{0} */ + d[i] -= points[i]; + /* 6 * S_{n-1} - S_{n} */ + k = len_int_points - (i+1); + l = len_points - (i+1); + d[k] -= points[l]; + } + /* The Thomas algorithm requires at least two points. Hence, + * `num_int_points` == 1 must be handled separately (let's call + * it "Mini Thomas"). */ + if (num_int_points == 1) { + for (i = 0; i < dimension; i++) + d[i] *= (tsReal) 0.25f; + } else { + TS_CALL(try, err, ts_int_thomas_algorithm( + a, b, c, num_int_points, dimension, d, + status)) + } + memcpy(thomas, points, sof_ctrlp); + memmove(thomas + dimension, d, num_int_points * sof_ctrlp); + memcpy(thomas + (num_int_points+1) * dimension, + points + (num_points-1) * dimension, sof_ctrlp); + TS_CALL(try, err, ts_int_relaxed_uniform_cubic_bspline( + thomas, num_points, dimension, spline, status)) + TS_CATCH(err) + ts_bspline_free(spline); + TS_FINALLY + if (thomas) + free(thomas); + TS_END_TRY_RETURN(err) +} + +tsError +ts_bspline_interpolate_catmull_rom(const tsReal *points, + size_t num_points, + size_t dimension, + tsReal alpha, + const tsReal *first, + const tsReal *last, + tsReal epsilon, + tsBSpline *spline, + tsStatus *status) +{ + const size_t sof_real = sizeof(tsReal); + const size_t sof_ctrlp = dimension * sof_real; + const tsReal eps = (tsReal) fabs(epsilon); + tsReal *bs_ctrlp; /* Points to the control points of `spline`. */ + tsReal *cr_ctrlp; /**< The points to interpolate based on `points`. */ + size_t i, d; /**< Used in for loops. */ + tsError err; /**< Local error handling. */ + /* [https://en.wikipedia.org/wiki/ + * Centripetal_Catmull%E2%80%93Rom_spline] */ + tsReal t0, t1, t2, t3; /**< Catmull-Rom knots. */ + /* [https://stackoverflow.com/questions/30748316/ + * catmull-rom-interpolation-on-svg-paths/30826434#30826434] */ + tsReal c1, c2, d1, d2, m1, m2; /**< Used to calculate derivatives. */ + tsReal *p0, *p1, *p2, *p3; /**< Processed Catmull-Rom points. */ + + ts_int_bspline_init(spline); + if (dimension == 0) + TS_RETURN_0(status, TS_DIM_ZERO, "unsupported dimension: 0") + if (num_points == 0) + TS_RETURN_0(status, TS_NUM_POINTS, "num(points) == 0") + if (alpha < (tsReal) 0.0) alpha = (tsReal) 0.0; + if (alpha > (tsReal) 1.0) alpha = (tsReal) 1.0; + + /* Copy `points` to `cr_ctrlp`. Add space for `first` and `last`. */ + cr_ctrlp = (tsReal *) malloc((num_points + 2) * sof_ctrlp); + if (!cr_ctrlp) + TS_RETURN_0(status, TS_MALLOC, "out of memory") + memcpy(cr_ctrlp + dimension, points, num_points * sof_ctrlp); + + /* Remove redundant points from `cr_ctrlp`. Update `num_points`. */ + for (i = 1 /* 0 (`first`) is not assigned yet */; + i < num_points /* skip last point (inclusive end) */; + i++) { + p0 = cr_ctrlp + (i * dimension); + p1 = p0 + dimension; + if (ts_distance(p0, p1, dimension) <= eps) { + /* Are there any other points (after the one that is + * to be removed) that need to be shifted? */ + if (i < num_points - 1) { + memmove(p1, p1 + dimension, + (num_points - (i + 1)) * sof_ctrlp); + } + num_points--; + i--; + } + } + + /* Check if there are still enough points for interpolation. */ + if (num_points == 1) { /* `num_points` can't be 0 */ + free(cr_ctrlp); /* The point is copied from `points`. */ + TS_CALL_ROE(err, ts_int_cubic_point( + points, dimension, spline, status)) + TS_RETURN_SUCCESS(status) + } + + /* Add or generate `first` and `last`. Update `num_points`. */ + p0 = cr_ctrlp + dimension; + if (first && ts_distance(first, p0, dimension) > eps) { + memcpy(cr_ctrlp, first, sof_ctrlp); + } else { + p1 = p0 + dimension; + for (d = 0; d < dimension; d++) + cr_ctrlp[d] = p0[d] + (p0[d] - p1[d]); + } + p1 = cr_ctrlp + (num_points * dimension); + if (last && ts_distance(p1, last, dimension) > eps) { + memcpy(cr_ctrlp + ((num_points + 1) * dimension), + last, sof_ctrlp); + } else { + p0 = p1 - dimension; + for (d = 0; d < dimension; d++) { + cr_ctrlp[((num_points + 1) * dimension) + d] = + p1[d] + (p1[d] - p0[d]); + } + } + num_points = num_points + 2; + + /* Transform the sequence of Catmull-Rom splines. */ + bs_ctrlp = NULL; + TS_TRY(try, err, status) + TS_CALL(try, err, ts_bspline_new( + (num_points - 3) * 4, dimension, 3, + TS_BEZIERS, spline, status)) + bs_ctrlp = ts_int_bspline_access_ctrlp(spline); + TS_CATCH(err) + free(cr_ctrlp); + TS_END_TRY_ROE(err) + for (i = 0; i < ts_bspline_num_control_points(spline) / 4; i++) { + p0 = cr_ctrlp + ((i+0) * dimension); + p1 = cr_ctrlp + ((i+1) * dimension); + p2 = cr_ctrlp + ((i+2) * dimension); + p3 = cr_ctrlp + ((i+3) * dimension); + + t0 = (tsReal) 0.f; + t1 = t0 + (tsReal) pow(ts_distance(p0, p1, dimension), alpha); + t2 = t1 + (tsReal) pow(ts_distance(p1, p2, dimension), alpha); + t3 = t2 + (tsReal) pow(ts_distance(p2, p3, dimension), alpha); + + c1 = (t2-t1) / (t2-t0); + c2 = (t1-t0) / (t2-t0); + d1 = (t3-t2) / (t3-t1); + d2 = (t2-t1) / (t3-t1); + + for (d = 0; d < dimension; d++) { + m1 = (t2-t1)*(c1*(p1[d]-p0[d])/(t1-t0) + + c2*(p2[d]-p1[d])/(t2-t1)); + m2 = (t2-t1)*(d1*(p2[d]-p1[d])/(t2-t1) + + d2*(p3[d]-p2[d])/(t3-t2)); + bs_ctrlp[((i*4 + 0) * dimension) + d] = p1[d]; + bs_ctrlp[((i*4 + 1) * dimension) + d] = p1[d] + m1/3; + bs_ctrlp[((i*4 + 2) * dimension) + d] = p2[d] - m2/3; + bs_ctrlp[((i*4 + 3) * dimension) + d] = p2[d]; + } + } + free(cr_ctrlp); + TS_RETURN_SUCCESS(status) +} +/*! @} */ + + + +/*! @name Query Functions + * + * @{ + */ +tsError +ts_int_bspline_find_knot(const tsBSpline *spline, + tsReal *knot, /* in: knot; out: actual knot */ + size_t *idx, /* out: index of `knot' */ + size_t *mult, /* out: multiplicity of `knot' */ + tsStatus *status) +{ + const size_t deg = ts_bspline_degree(spline); + const size_t num_knots = ts_bspline_num_knots(spline); + const tsReal *knots = ts_int_bspline_access_knots(spline); + tsReal min, max; + size_t low, high; + + ts_bspline_domain(spline, &min, &max); + if (*knot < min) { + /* Avoid infinite loop (issue #222) */ + if (ts_knots_equal(*knot, min)) *knot = min; + else { + TS_RETURN_2(status, TS_U_UNDEFINED, + "knot (%f) < min(domain) (%f)", + *knot, min) + } + } + else if (*knot > max && !ts_knots_equal(*knot, max)) { + TS_RETURN_2(status, TS_U_UNDEFINED, + "knot (%f) > max(domain) (%f)", + *knot, max) + } + + /* Based on 'The NURBS Book' (Les Piegl and Wayne Tiller). */ + if (ts_knots_equal(*knot, knots[num_knots - 1])) { + *idx = num_knots - 1; + } else { + low = 0; + high = num_knots - 1; + *idx = (low+high) / 2; + while (*knot < knots[*idx] || *knot >= knots[*idx + 1]) { + if (*knot < knots[*idx]) + high = *idx; + else + low = *idx; + *idx = (low+high) / 2; + } + } + + /* Handle floating point errors. */ + while (*idx < num_knots - 1 && /* there is a next knot */ + ts_knots_equal(*knot, knots[*idx + 1])) { + (*idx)++; + } + if (ts_knots_equal(*knot, knots[*idx])) + *knot = knots[*idx]; /* set actual knot */ + + /* Calculate knot's multiplicity. */ + for (*mult = deg + 1; *mult > 0 ; (*mult)--) { + if (ts_knots_equal(*knot, knots[*idx - (*mult-1)])) + break; + } + + TS_RETURN_SUCCESS(status) +} + +tsError +ts_int_bspline_eval_woa(const tsBSpline *spline, + tsReal u, + tsDeBoorNet *net, + tsStatus *status) +{ + const size_t deg = ts_bspline_degree(spline); + const size_t order = ts_bspline_order(spline); + const size_t dim = ts_bspline_dimension(spline); + const size_t num_knots = ts_bspline_num_knots(spline); + const size_t sof_ctrlp = dim * sizeof(tsReal); + + const tsReal *ctrlp = ts_int_bspline_access_ctrlp(spline); + const tsReal *knots = ts_int_bspline_access_knots(spline); + tsReal *points = NULL; /**< Pointer to the points of \p net. */ + + size_t k; /**< Index of \p u. */ + size_t s; /**< Multiplicity of \p u. */ + + size_t from; /**< Offset used to copy values. */ + size_t fst; /**< First affected control point, inclusive. */ + size_t lst; /**< Last affected control point, inclusive. */ + size_t N; /**< Number of affected control points. */ + + /* The following indices are used to create the DeBoor net. */ + size_t lidx; /**< Current left index. */ + size_t ridx; /**< Current right index. */ + size_t tidx; /**< Current to index. */ + size_t r, i, d; /**< Used in for loop. */ + tsReal ui; /**< Knot value at index i. */ + tsReal a, a_hat; /**< Weighting factors of control points. */ + + tsError err; + + points = ts_int_deboornet_access_points(net); + + /* 1. Find index k such that u is in between [u_k, u_k+1). + * 2. Setup already known values. + * 3. Decide by multiplicity of u how to calculate point P(u). */ + + /* 1. */ + k = s = 0; + TS_CALL_ROE(err, ts_int_bspline_find_knot( + spline, &u, &k, &s, status)) + + /* 2. */ + net->pImpl->u = u; + net->pImpl->k = k; + net->pImpl->s = s; + net->pImpl->h = deg < s ? 0 : deg-s; /* prevent underflow */ + + /* 3. (by 1. s <= order) + * + * 3a) Check for s = order. + * Take the two points k-s and k-s + 1. If one of + * them doesn't exist, take only the other. + * 3b) Use de boor algorithm to find point P(u). */ + if (s == order) { + /* only one of the two control points exists */ + if (k == deg || /* only the first */ + k == num_knots - 1) { /* only the last */ + from = k == deg ? 0 : (k-s) * dim; + net->pImpl->n_points = 1; + memcpy(points, ctrlp + from, sof_ctrlp); + } else { + from = (k-s) * dim; + net->pImpl->n_points = 2; + memcpy(points, ctrlp + from, 2 * sof_ctrlp); + } + } else { /* by 3a) s <= deg (order = deg+1) */ + fst = k-deg; /* by 1. k >= deg */ + lst = k-s; /* s <= deg <= k */ + N = lst-fst + 1; /* lst <= fst implies N >= 1 */ + + net->pImpl->n_points = (size_t)(N * (N+1) * 0.5f); + + /* copy initial values to output */ + memcpy(points, ctrlp + fst*dim, N * sof_ctrlp); + + lidx = 0; + ridx = dim; + tidx = N*dim; /* N >= 1 implies tidx > 0 */ + r = 1; + for (;r <= ts_deboornet_num_insertions(net); r++) { + i = fst + r; + for (; i <= lst; i++) { + ui = knots[i]; + a = (ts_deboornet_knot(net) - ui) / + (knots[i+deg-r+1] - ui); + a_hat = 1.f-a; + + for (d = 0; d < dim; d++) { + points[tidx++] = + a_hat * points[lidx++] + + a * points[ridx++]; + } + } + lidx += dim; + ridx += dim; + } + } + TS_RETURN_SUCCESS(status) +} + +tsError +ts_bspline_eval(const tsBSpline *spline, + tsReal knot, + tsDeBoorNet *net, + tsStatus *status) +{ + tsError err; + ts_int_deboornet_init(net); + TS_TRY(try, err, status) + TS_CALL(try, err, ts_int_deboornet_new( + spline, net, status)) + TS_CALL(try, err, ts_int_bspline_eval_woa( + spline, knot, net, status)) + TS_CATCH(err) + ts_deboornet_free(net); + TS_END_TRY_RETURN(err) +} + +tsError +ts_bspline_eval_all(const tsBSpline *spline, + const tsReal *knots, + size_t num, + tsReal **points, + tsStatus *status) +{ + const size_t dim = ts_bspline_dimension(spline); + const size_t sof_point = dim * sizeof(tsReal); + const size_t sof_points = num * sof_point; + tsDeBoorNet net = ts_deboornet_init(); + tsReal *result; + size_t i; + tsError err; + TS_TRY(try, err, status) + *points = (tsReal *) malloc(sof_points); + if (!*points) { + TS_THROW_0(try, err, status, TS_MALLOC, + "out of memory") + } + TS_CALL(try, err, ts_int_deboornet_new( + spline,&net, status)) + for (i = 0; i < num; i++) { + TS_CALL(try, err, ts_int_bspline_eval_woa( + spline, knots[i], &net, status)) + result = ts_int_deboornet_access_result(&net); + memcpy((*points) + i * dim, result, sof_point); + } + TS_CATCH(err) + if (*points) + free(*points); + *points = NULL; + TS_FINALLY + ts_deboornet_free(&net); + TS_END_TRY_RETURN(err) +} + +tsError +ts_bspline_sample(const tsBSpline *spline, + size_t num, + tsReal **points, + size_t *actual_num, + tsStatus *status) +{ + tsError err; + tsReal *knots; + + num = num == 0 ? 100 : num; + *actual_num = num; + knots = (tsReal *) malloc(num * sizeof(tsReal)); + if (!knots) { + *points = NULL; + TS_RETURN_0(status, TS_MALLOC, "out of memory") + } + ts_bspline_uniform_knot_seq(spline, num, knots); + TS_TRY(try, err, status) + TS_CALL(try, err, ts_bspline_eval_all( + spline, knots, num, points, status)) + TS_FINALLY + free(knots); + TS_END_TRY_RETURN(err) +} + +tsError +ts_bspline_bisect(const tsBSpline *spline, + tsReal value, + tsReal epsilon, + int persnickety, + size_t index, + int ascending, + size_t max_iter, + tsDeBoorNet *net, + tsStatus *status) +{ + tsError err; + const size_t dim = ts_bspline_dimension(spline); + const tsReal eps = (tsReal) fabs(epsilon); + size_t i = 0; + tsReal dist = 0; + tsReal min, max, mid; + tsReal *P; + + ts_int_deboornet_init(net); + + if (dim < index) { + TS_RETURN_2(status, TS_INDEX_ERROR, + "dimension (%lu) <= index (%lu)", + (unsigned long) dim, + (unsigned long) index) + } + if(max_iter == 0) + TS_RETURN_0(status, TS_NO_RESULT, "0 iterations") + + ts_bspline_domain(spline, &min, &max); + TS_TRY(try, err, status) + TS_CALL(try, err, ts_int_deboornet_new( + spline, net, status)) + do { + mid = (tsReal) ((min + max) / 2.0); + TS_CALL(try, err, ts_int_bspline_eval_woa( + spline, mid, net, status)) + P = ts_int_deboornet_access_result(net); + dist = ts_distance(&P[index], &value, 1); + if (dist <= eps) + TS_RETURN_SUCCESS(status) + if (ascending) { + if (P[index] < value) + min = mid; + else + max = mid; + } else { + if (P[index] < value) + max = mid; + else + min = mid; + } + } while (i++ < max_iter); + if (persnickety) { + TS_THROW_1(try, err, status, TS_NO_RESULT, + "maximum iterations (%lu) exceeded", + (unsigned long) max_iter) + } + TS_CATCH(err) + ts_deboornet_free(net); + TS_END_TRY_RETURN(err) +} + +void ts_bspline_domain(const tsBSpline *spline, + tsReal *min, + tsReal *max) +{ + *min = ts_int_bspline_access_knots(spline) + [ts_bspline_degree(spline)]; + *max = ts_int_bspline_access_knots(spline) + [ts_bspline_num_knots(spline) - ts_bspline_order(spline)]; +} + +tsError +ts_bspline_is_closed(const tsBSpline *spline, + tsReal epsilon, + int *closed, + tsStatus *status) +{ + const size_t deg = ts_bspline_degree(spline); + const size_t dim = ts_bspline_dimension(spline); + tsBSpline derivative; + tsReal min, max; + tsDeBoorNet first, last; + size_t i; + tsError err; + + ts_int_bspline_init(&derivative); + ts_int_deboornet_init(&first); + ts_int_deboornet_init(&last); + + TS_TRY(try, err, status) + for (i = 0; i < deg; i++) { + TS_CALL(try, err, ts_bspline_derive( + spline, i, -1.f, &derivative, status)) + ts_bspline_domain(&derivative, &min, &max); + TS_CALL(try, err, ts_bspline_eval( + &derivative, min, &first, status)) + TS_CALL(try, err, ts_bspline_eval( + &derivative, max, &last, status)) + *closed = ts_distance( + ts_int_deboornet_access_result(&first), + ts_int_deboornet_access_result(&last), + dim) <= epsilon ? 1 : 0; + ts_bspline_free(&derivative); + ts_deboornet_free(&first); + ts_deboornet_free(&last); + if (!*closed) + TS_RETURN_SUCCESS(status) + } + TS_FINALLY + ts_bspline_free(&derivative); + ts_deboornet_free(&first); + ts_deboornet_free(&last); + TS_END_TRY_RETURN(err) +} + +tsError +ts_bspline_compute_rmf(const tsBSpline *spline, + const tsReal *knots, + size_t num, + int has_first_normal, + tsFrame *frames, + tsStatus *status) +{ + tsError err; + size_t i; + tsReal fx, fy, fz, fmin; + tsReal xc[3], xn[3], v1[3], c1, v2[3], c2, rL[3], tL[3]; + tsBSpline deriv = ts_bspline_init(); + tsDeBoorNet curr = ts_deboornet_init(); + tsDeBoorNet next = ts_deboornet_init(); + + if (num < 1) + TS_RETURN_SUCCESS(status); + + TS_TRY(try, err, status) + TS_CALL(try, err, ts_int_deboornet_new( + spline, &curr, status)) + TS_CALL(try, err, ts_int_deboornet_new( + spline, &next, status)) + TS_CALL(try, err, ts_bspline_derive( + spline, 1, (tsReal) -1.0, &deriv, status)) + + /* Set position. */ + TS_CALL(try, err, ts_int_bspline_eval_woa( + spline, knots[0], &curr, status)) + ts_vec3_set(frames[0].position, + ts_int_deboornet_access_result(&curr), + ts_bspline_dimension(spline)); + /* Set tangent. */ + TS_CALL(try, err, ts_int_bspline_eval_woa( + &deriv, knots[0], &curr, status)) + ts_vec3_set(frames[0].tangent, + ts_int_deboornet_access_result(&curr), + ts_bspline_dimension(&deriv)); + ts_vec_norm(frames[0].tangent, 3, frames[0].tangent); + /* Set normal. */ + if (!has_first_normal) { + fx = (tsReal) fabs(frames[0].tangent[0]); + fy = (tsReal) fabs(frames[0].tangent[1]); + fz = (tsReal) fabs(frames[0].tangent[2]); + fmin = fx; /* x is min => 1, 0, 0 */ + ts_vec3_init(frames[0].normal, + (tsReal) 1.0, + (tsReal) 0.0, + (tsReal) 0.0); + if (fy < fmin) { /* y is min => 0, 1, 0 */ + fmin = fy; + ts_vec3_init(frames[0].normal, + (tsReal) 0.0, + (tsReal) 1.0, + (tsReal) 0.0); + } + if (fz < fmin) { /* z is min => 0, 0, 1 */ + ts_vec3_init(frames[0].normal, + (tsReal) 0.0, + (tsReal) 0.0, + (tsReal) 1.0); + } + ts_vec3_cross(frames[0].tangent, + frames[0].normal, + frames[0].normal); + ts_vec_norm(frames[0].normal, 3, frames[0].normal); + if (ts_bspline_dimension(spline) >= 3) { + /* In 3D (and higher) an additional rotation of + the normal along the tangent is needed in + order to let the normal extend sideways (as + it does in 2D and lower). */ + ts_vec3_cross(frames[0].tangent, + frames[0].normal, + frames[0].normal); + } + } else { + /* Never trust user input! */ + ts_vec_norm(frames[0].normal, 3, frames[0].normal); + } + /* Set binormal. */ + ts_vec3_cross(frames[0].tangent, + frames[0].normal, + frames[0].binormal); + + for (i = 0; i < num - 1; i++) { + /* Eval current and next point. */ + TS_CALL(try, err, ts_int_bspline_eval_woa( + spline, knots[i], &curr, status)) + TS_CALL(try, err, ts_int_bspline_eval_woa( + spline, knots[i+1], &next, status)) + ts_vec3_set(xc, /* xc is now the current point */ + ts_int_deboornet_access_result(&curr), + ts_bspline_dimension(spline)); + ts_vec3_set(xn, /* xn is now the next point */ + ts_int_deboornet_access_result(&next), + ts_bspline_dimension(spline)); + + /* Set position of U_{i+1}. */ + ts_vec3_set(frames[i+1].position, xn, 3); + + /* Compute reflection vector of R_{1}. */ + ts_vec_sub(xn, xc, 3, v1); + c1 = ts_vec_dot(v1, v1, 3); + + /* Compute r_{i}^{L} = R_{1} * r_{i}. */ + rL[0] = (tsReal) 2.0 / c1; + rL[1] = ts_vec_dot(v1, frames[i].normal, 3); + rL[2] = rL[0] * rL[1]; + ts_vec_mul(v1, 3, rL[2], rL); + ts_vec_sub(frames[i].normal, rL, 3, rL); + + /* Compute t_{i}^{L} = R_{1} * t_{i}. */ + tL[0] = (tsReal) 2.0 / c1; + tL[1] = ts_vec_dot(v1, frames[i].tangent, 3); + tL[2] = tL[0] * tL[1]; + ts_vec_mul(v1, 3, tL[2], tL); + ts_vec_sub(frames[i].tangent, tL, 3, tL); + + /* Compute reflection vector of R_{2}. */ + TS_CALL(try, err, ts_int_bspline_eval_woa( + &deriv, knots[i+1], &next, status)) + ts_vec3_set(xn, /* xn is now the next tangent */ + ts_int_deboornet_access_result(&next), + ts_bspline_dimension(&deriv)); + ts_vec_norm(xn, 3, xn); + ts_vec_sub(xn, tL, 3, v2); + c2 = ts_vec_dot(v2, v2, 3); + + /* Compute r_{i+1} = R_{2} * r_{i}^{L}. */ + ts_vec3_set(xc, /* xc is now the next normal */ + frames[i+1].normal, 3); + xc[0] = (tsReal) 2.0 / c2; + xc[1] = ts_vec_dot(v2, rL, 3); + xc[2] = xc[0] * xc[1]; + ts_vec_mul(v2, 3, xc[2], xc); + ts_vec_sub(rL, xc, 3, xc); + ts_vec_norm(xc, 3, xc); + + /* Compute vector s_{i+1} of U_{i+1}. */ + ts_vec3_cross(xn, xc, frames[i+1].binormal); + + /* Set vectors t_{i+1} and r_{i+1} of U_{i+1}. */ + ts_vec3_set(frames[i+1].tangent, xn, 3); + ts_vec3_set(frames[i+1].normal, xc, 3); + } + TS_FINALLY + ts_bspline_free(&deriv); + ts_deboornet_free(&curr); + ts_deboornet_free(&next); + TS_END_TRY_RETURN(err) } -void ts_internal_bspline_find_u( const tsBSpline* bspline, const tsReal u, - size_t* k, size_t* s, jmp_buf buf - ) +tsError +ts_bspline_chord_lengths(const tsBSpline *spline, + const tsReal *knots, + size_t num, + tsReal *lengths, + tsStatus *status) { - const size_t deg = bspline->deg; - const size_t order = bspline->order; - const size_t n_knots = bspline->n_knots; + tsError err; + tsReal dist, lst_knot, cur_knot; + size_t i, dim = ts_bspline_dimension(spline); + tsDeBoorNet lst = ts_deboornet_init(); + tsDeBoorNet cur = ts_deboornet_init(); + tsDeBoorNet tmp = ts_deboornet_init(); - *k = *s = 0; + if (num == 0) TS_RETURN_SUCCESS(status); - for( ; *k < n_knots; (*k)++ ) - { - const tsReal uk = bspline->knots[*k]; + TS_TRY(try, err, status) + TS_CALL(try, err, ts_int_deboornet_new( + spline, &lst, status)) + TS_CALL(try, err, ts_int_deboornet_new( + spline, &cur, status)) - if( ts_fequals( u, uk ) ) - { - (*s)++; - } - else if( u < uk ) - { - break; - } - } + /* num >= 1 */ + TS_CALL(try, err, ts_int_bspline_eval_woa( + spline, knots[0], &lst, status)); + lengths[0] = (tsReal) 0.0; - /* keep in mind that currently k is k+1 */ - if( *s > order ) - longjmp( buf, TS_MULTIPLICITY ); - - if( *k <= deg ) /* u < u_min */ - longjmp( buf, TS_U_UNDEFINED ); - - if( *k == n_knots && *s == 0 ) /* u > u_last */ - longjmp( buf, TS_U_UNDEFINED ); - - if( *k > n_knots - deg + *s - 1 ) /* u > u_max */ - longjmp( buf, TS_U_UNDEFINED ); - - (*k)--; /* k+1 - 1 will never underflow */ + for (i = 1; i < num; i++) { + TS_CALL(try, err, ts_int_bspline_eval_woa( + spline, knots[i], &cur, status)); + lst_knot = ts_deboornet_knot(&lst); + cur_knot = ts_deboornet_knot(&cur); + if (cur_knot < lst_knot) { + TS_THROW_1(try, err, status, TS_KNOTS_DECR, + "decreasing knot at index: %lu", + (unsigned long) i) + } + dist = ts_distance(ts_deboornet_result_ptr(&lst), + ts_deboornet_result_ptr(&cur), + dim); + lengths[i] = lengths[i-1] + dist; + ts_deboornet_move(&lst, &tmp); + ts_deboornet_move(&cur, &lst); + ts_deboornet_move(&tmp, &cur); + } + TS_FINALLY + ts_deboornet_free(&lst); + ts_deboornet_free(&cur); + TS_END_TRY_RETURN(err) } -void ts_internal_bspline_copy( const tsBSpline* original, - tsBSpline* copy, jmp_buf buf - ) +tsError +ts_bspline_sub_spline(const tsBSpline *spline, + tsReal knot0, + tsReal knot1, + tsBSpline *sub, + tsStatus *status) { - const size_t dim = original->dim; - const size_t sof_f = sizeof(tsReal); - const size_t n_ctrlp = original->n_ctrlp; - const size_t n_knots = original->n_knots; - const size_t sof_ck = (n_ctrlp * dim + n_knots) * sof_f; + int reverse; /* reverse `spline`? (if `knot0 > knot1`) */ + tsReal *tmp = NULL; /* a buffer to swap control points */ + tsReal min, max; /* domain of `spline` */ + size_t dim, deg, order; /* properties of `spline` (and `sub`) */ + tsBSpline worker; /* stores the result of the `split' operations */ + tsReal *ctrlp, *knots; /* control points and knots of `worker` */ + size_t k0, k1; /* indices returned by the `split' operations */ + size_t c0, c1; /* indices of the control points to be moved */ + size_t nc, nk; /* number of control points and knots of `sub` */ + size_t i; /* for various needs */ + tsError err; /* for local try-catch block */ - /* Nothing to do here. */ - if( original == copy ) - return; + /* Make sure that `worker` points to `NULL'. This allows us to call + * `ts_bspline_free` in `TS_CATCH` without further checks. Also, `NULL' + * serves as an indicator of whether `ts_bspline_split` has been called + * on `spline` at least once (if not, `worker` needs to be initialized + * manually). */ + ts_int_bspline_init(&worker); + INIT_OUT_BSPLINE(spline, sub) - copy->deg = original->deg; - copy->order = original->order; - copy->dim = original->dim; - copy->n_ctrlp = original->n_ctrlp; - copy->n_knots = original->n_knots; - copy->ctrlp = (tsReal*) malloc( sof_ck ); + ts_bspline_domain(spline, &min, &max); + dim = ts_bspline_dimension(spline); + deg = ts_bspline_degree(spline); + order = ts_bspline_order(spline); - if( copy->ctrlp == NULL ) - longjmp( buf, TS_MALLOC ); + /* Cannot create valid knot vector from empty domain. */ + if (ts_knots_equal(knot0, knot1)) { + TS_RETURN_0(status, + TS_NO_RESULT, + "empty domain") + } - memcpy( copy->ctrlp, original->ctrlp, sof_ck ); - copy->knots = copy->ctrlp + n_ctrlp * dim; + /* Check for `reverse mode'. Reverse mode means that the copied sequence + * of (sub) control points need to be reversed, forming a `backwards' + * spline. */ + reverse = knot0 > knot1; + if (reverse) { /* swap `knot0` and `knot1` */ + tmp = (tsReal *) malloc(dim * sizeof(tsReal)); + if (!tmp) TS_RETURN_0(status, TS_MALLOC, "out of memory"); + *tmp = knot0; /* `tmp` can hold at least one value */ + knot0 = knot1; + knot1 = *tmp; + } + + TS_TRY(try, err, status) + if (!ts_knots_equal(knot0 , min)) { + TS_CALL(try , err, ts_bspline_split( + spline, knot0, &worker, &k0, status)) + } else { k0 = deg; } + if (!ts_knots_equal(knot1, max)) { + TS_CALL(try , err, ts_bspline_split( + /* If `NULL', the split operation + above was not called. */ + !worker.pImpl ? spline : &worker, + knot1, &worker, &k1, status)) + } else { + k1 = ts_bspline_num_knots( + /* If `NULL', the split operation + above was not called. */ + !worker.pImpl ? spline : &worker) - 1; + } + + /* Set up `worker`. */ + if (!worker.pImpl) { /* => no split applied */ + TS_CALL(try, err, ts_bspline_copy( + spline, &worker, status)) + /* Needed in `reverse mode'. */ + ctrlp = ts_int_bspline_access_ctrlp(&worker); + knots = ts_int_bspline_access_knots(&worker); + nc = ts_bspline_num_control_points(&worker); + } else { + c0 = (k0-deg) * dim; + c1 = (k1-order) * dim; + nc = ((c1-c0) / dim) + 1; + nk = (k1-k0) + order; + + /* Also needed in `reverse mode'. */ + ctrlp = ts_int_bspline_access_ctrlp(&worker); + knots = ts_int_bspline_access_knots(&worker); + + /* Move control points. */ + memmove(ctrlp, + ctrlp + c0, + nc * dim * sizeof(tsReal)); + /* Move knots. */ + memmove(ctrlp + nc * dim, + knots + (k0-deg), + nk * sizeof(tsReal)); + + /* Remove superfluous control points and knots from + * the memory of `worker`. */ + worker.pImpl->n_knots = nk; + worker.pImpl->n_ctrlp = nc; + i = ts_int_bspline_sof_state(&worker); + worker.pImpl = realloc(worker.pImpl, i); + if (worker.pImpl == NULL) { /* unlikely to fail */ + TS_THROW_0(try, err, status, TS_MALLOC, + "out of memory") + } + } + + /* Reverse control points (if necessary). */ + if (reverse) { + for (i = 0; i < nc / 2; i++) { + memcpy(tmp, + ctrlp + i * dim, + dim * sizeof(tsReal)); + memmove(ctrlp + i * dim, + ctrlp + (nc-1 - i) * dim, + dim * sizeof(tsReal)); + memcpy(ctrlp + (nc-1 - i) * dim, + tmp, + dim * sizeof(tsReal)); + } + } + + /* Move `worker' to output parameter. */ + if (spline == sub) ts_bspline_free(sub); + ts_bspline_move(&worker, sub); + TS_CATCH(err) + ts_bspline_free(&worker); + TS_FINALLY + if (tmp) free(tmp); + TS_END_TRY_RETURN(err) } - -void ts_internal_bspline_fill_knots( const tsBSpline* original, const tsBSplineType type, - const tsReal min, const tsReal max, - tsBSpline* result, jmp_buf buf - ) +void +ts_bspline_uniform_knot_seq(const tsBSpline *spline, + size_t num, + tsReal *knots) { - const size_t n_knots = original->n_knots; - const size_t deg = original->deg; - const size_t order = deg + 1; /* Using deg+1 instead of original->order - * ensures order >= 1. */ - tsReal fac; /* The factor used to calculate the knot values. */ - size_t i; /* Used in for loops. */ - - /* order >= 1 implies 2*order >= 2 implies n_knots >= 2 */ - if( n_knots < 2 * order ) - longjmp( buf, TS_DEG_GE_NCTRLP ); - - if( type == TS_BEZIERS && n_knots % order != 0 ) - longjmp( buf, TS_NUM_KNOTS ); - - if( min > max || ts_fequals( min, max ) ) - longjmp( buf, TS_KNOTS_DECR ); - - /* copy spline even if type is TS_NONE */ - ts_internal_bspline_copy( original, result, buf ); - - if( type == TS_OPENED ) - { - /* ensures that the first knot value is exactly \min */ - result->knots[0] = min; /* n_knots >= 2 */ - - fac = (max - min) / (n_knots - 1); /* n_knots >= 2 */ - - for( i = 1; i < n_knots - 1; i++ ) - result->knots[i] = min + i * fac; - - /* ensure that the last knot value is exactly \max */ - result->knots[i] = max; - } - else if( type == TS_CLAMPED ) - { - /* n_knots >= 2*order == 2*(deg+1) == 2*deg + 2 > 2*deg - 1 */ - fac = (max - min) / (n_knots - 2 * deg - 1); - - ts_arr_fill( result->knots, order, min ); - - for( i = order; i < n_knots - order; i++ ) - result->knots[i] = min + (i - deg) * fac; - - ts_arr_fill( result->knots + i, order, max ); - } - else if( type == TS_BEZIERS ) - { - /* n_knots >= 2*order implies n_knots/order >= 2 */ - fac = (max - min) / (n_knots / order - 1); - - ts_arr_fill( result->knots, order, min ); - - for( i = order; i < n_knots - order; i += order ) - ts_arr_fill( result->knots + i, order, min + (i / order) * fac ); - - ts_arr_fill( result->knots + i, order, max ); - } + size_t i; + tsReal min, max; + if (num == 0) return; + ts_bspline_domain(spline, &min, &max); + for (i = 0; i < num; i++) { + knots[i] = max - min; + knots[i] *= (tsReal) i / (num - 1); + knots[i] += min; + } + /* Set `knots[0]` after `knots[num - 1]` to ensure + that `knots[0] = min` if `num` is `1'. */ + knots[num - 1] = max; + knots[0] = min; } - -void ts_internal_bspline_new( const size_t n_ctrlp, const size_t dim, const size_t deg, - const tsBSplineType type, tsBSpline* bspline, jmp_buf buf - ) +tsError +ts_bspline_equidistant_knot_seq(const tsBSpline *spline, + size_t num, + tsReal *knots, + size_t num_samples, + tsStatus *status) { - const size_t order = deg + 1; - const size_t n_knots = n_ctrlp + order; - const size_t sof_f = sizeof(tsReal); - const size_t sof_ck = (n_ctrlp * dim + n_knots) * sof_f; - tsError e; - jmp_buf b; + tsError err; + tsReal *samples = NULL, *lengths = NULL; - if( dim < 1 ) - longjmp( buf, TS_DIM_ZERO ); + if (num == 0) TS_RETURN_SUCCESS(status); + if (num_samples == 0) num_samples = 200; - if( deg >= n_ctrlp ) - longjmp( buf, TS_DEG_GE_NCTRLP ); + samples = (tsReal *) malloc(2 * num_samples * sizeof(tsReal)); + if (!samples) TS_RETURN_0(status, TS_MALLOC, "out of memory"); + ts_bspline_uniform_knot_seq(spline, num_samples, samples); + lengths = samples + num_samples; + TS_TRY(try, err, status) + TS_CALL(try, err, ts_bspline_chord_lengths( + spline, samples, num_samples, lengths, status)) + TS_CALL(try, err, ts_chord_lengths_equidistant_knot_seq( + samples, lengths, num_samples, num, knots, status)) + TS_FINALLY + free(samples); /* cannot be NULL */ + /* free(lengths); NO! */ + TS_END_TRY_RETURN(err) +} +/*! @} */ - bspline->deg = deg; - bspline->order = order; - bspline->dim = dim; - bspline->n_ctrlp = n_ctrlp; - bspline->n_knots = n_knots; - bspline->ctrlp = (tsReal*) malloc( sof_ck ); - if( bspline->ctrlp == NULL ) - longjmp( buf, TS_MALLOC ); - bspline->knots = bspline->ctrlp + n_ctrlp * dim; +/*! @name Transformation Functions + * + * @{ + */ +tsError +ts_int_bspline_resize(const tsBSpline *spline, + int n, + int back, + tsBSpline *resized, + tsStatus *status) +{ + const size_t deg = ts_bspline_degree(spline); + const size_t dim = ts_bspline_dimension(spline); + const size_t sof_real = sizeof(tsReal); - TRY( b, e ) - ts_internal_bspline_fill_knots( bspline, type, 0.f, 1.f, bspline, b ); - CATCH free( bspline->ctrlp ); + const size_t num_ctrlp = ts_bspline_num_control_points(spline); + const size_t num_knots = ts_bspline_num_knots(spline); + const size_t nnum_ctrlp = num_ctrlp + n; /**< New length of ctrlp. */ + const size_t nnum_knots = num_knots + n; /**< New length of knots. */ + const size_t min_num_ctrlp_vec = n < 0 ? nnum_ctrlp : num_ctrlp; + const size_t min_num_knots_vec = n < 0 ? nnum_knots : num_knots; - longjmp( buf, e ); - ETRY + const size_t sof_min_num_ctrlp = min_num_ctrlp_vec * dim * sof_real; + const size_t sof_min_num_knots = min_num_knots_vec * sof_real; + + tsBSpline tmp; /**< Temporarily stores the result. */ + const tsReal* from_ctrlp = ts_int_bspline_access_ctrlp(spline); + const tsReal* from_knots = ts_int_bspline_access_knots(spline); + tsReal* to_ctrlp = NULL; /**< Pointer to the control points of tmp. */ + tsReal* to_knots = NULL; /**< Pointer to the knots of tmp. */ + + tsError err; + + if (n == 0) return ts_bspline_copy(spline, resized, status); + + INIT_OUT_BSPLINE(spline, resized) + TS_CALL_ROE(err, ts_bspline_new( + nnum_ctrlp, dim, deg, TS_OPENED, + &tmp, status)) + to_ctrlp = ts_int_bspline_access_ctrlp(&tmp); + to_knots = ts_int_bspline_access_knots(&tmp); + + /* Copy control points and knots. */ + if (!back && n < 0) { + memcpy(to_ctrlp, from_ctrlp - n*dim, sof_min_num_ctrlp); + memcpy(to_knots, from_knots - n , sof_min_num_knots); + } else if (!back && n > 0) { + memcpy(to_ctrlp + n*dim, from_ctrlp, sof_min_num_ctrlp); + memcpy(to_knots + n , from_knots, sof_min_num_knots); + } else { + /* n != 0 implies back == true */ + memcpy(to_ctrlp, from_ctrlp, sof_min_num_ctrlp); + memcpy(to_knots, from_knots, sof_min_num_knots); + } + + if (spline == resized) + ts_bspline_free(resized); + ts_bspline_move(&tmp, resized); + TS_RETURN_SUCCESS(status) } - -void ts_internal_bspline_resize( const tsBSpline* bspline, const int n, const int back, - tsBSpline* resized, jmp_buf buf - ) +tsError +ts_bspline_derive(const tsBSpline *spline, + size_t n, + tsReal epsilon, + tsBSpline *deriv, + tsStatus *status) { - const size_t deg = bspline->deg; - const size_t dim = bspline->dim; - const size_t sof_f = sizeof(tsReal); - const size_t sof_c = dim * sof_f; + const size_t sof_real = sizeof(tsReal); + const size_t dim = ts_bspline_dimension(spline); + const size_t sof_ctrlp = dim * sof_real; + size_t deg = ts_bspline_degree(spline); + size_t num_ctrlp = ts_bspline_num_control_points(spline); + size_t num_knots = ts_bspline_num_knots(spline); - const size_t n_ctrlp = bspline->n_ctrlp; - const size_t n_knots = bspline->n_knots; - const size_t nn_ctrlp = n_ctrlp + n; /* The new length of ctrlp. */ - const size_t nn_knots = n_knots + n; /* The new length of knots. */ - const size_t sof_ncnk = (nn_ctrlp * dim + nn_knots) * sof_f; - const size_t min_n_ctrlp = n < 0 ? nn_ctrlp : n_ctrlp; /* The minimum of - * the control points old and new size. */ - const size_t min_n_knots = n < 0 ? nn_knots : n_knots; /* the minimum of - * the knots old and new size. */ + tsBSpline worker; /**< Stores the intermediate result. */ + tsReal* ctrlp; /**< Pointer to the control points of worker. */ + tsReal* knots; /**< Pointer to the knots of worker. */ - tsReal* from_ctrlp = bspline->ctrlp; - tsReal* from_knots = bspline->knots; - tsReal* to_ctrlp = NULL; - tsReal* to_knots = NULL; + size_t m, i, j, k, l; /**< Used in for loops. */ + tsReal *fst, *snd; /**< Pointer to first and second control point. */ + tsReal dist; /**< Distance between fst and snd. */ + tsReal kid1, ki1; /**< Knots at i+deg+1 and i+1. */ + tsReal span; /**< Distance between kid1 and ki1. */ - /* If n is 0 the spline must not be resized. */ - if( n == 0 ) - { - ts_internal_bspline_copy( bspline, resized, buf ); - return; - } + tsBSpline swap; /**< Used to swap worker and derivative. */ + tsError err; - if( bspline != resized ) - { - ts_internal_bspline_new( nn_ctrlp, dim, deg, TS_NONE, resized, buf ); - to_ctrlp = resized->ctrlp; - to_knots = resized->knots; - } - else - { - if( nn_ctrlp <= deg ) - longjmp( buf, TS_DEG_GE_NCTRLP ); + INIT_OUT_BSPLINE(spline, deriv) + TS_CALL_ROE(err, ts_bspline_copy(spline, &worker, status)) + ctrlp = ts_int_bspline_access_ctrlp(&worker); + knots = ts_int_bspline_access_knots(&worker); - to_ctrlp = (tsReal*) malloc( sof_ncnk ); - - if( to_ctrlp == NULL ) - longjmp( buf, TS_MALLOC ); - - to_knots = to_ctrlp + nn_ctrlp * dim; - } - - /* Copy control points and knots. */ - if( !back && n < 0 ) - { - memcpy( to_ctrlp, from_ctrlp - n * dim, min_n_ctrlp * sof_c ); - memcpy( to_knots, from_knots - n, min_n_knots * sof_f ); - } - else if( !back && n > 0 ) - { - memcpy( to_ctrlp + n * dim, from_ctrlp, min_n_ctrlp * sof_c ); - memcpy( to_knots + n, from_knots, min_n_knots * sof_f ); - } - else - { - /* n != 0 implies back == true */ - memcpy( to_ctrlp, from_ctrlp, min_n_ctrlp * sof_c ); - memcpy( to_knots, from_knots, min_n_knots * sof_f ); - } - - /* Cleanup if necessary. */ - if( bspline == resized ) - { - /* free old memory */ - free( from_ctrlp ); - /* assign new values */ - resized->ctrlp = to_ctrlp; - resized->knots = to_knots; - resized->n_ctrlp = nn_ctrlp; - resized->n_knots = nn_knots; - } + TS_TRY(try, err, status) + for (m = 1; m <= n; m++) { /* from 1st to n'th derivative */ + if (deg == 0) { + ts_arr_fill(ctrlp, dim, 0.f); + ts_bspline_domain(spline, + &knots[0], + &knots[1]); + num_ctrlp = 1; + num_knots = 2; + break; + } + /* Check and, if possible, fix discontinuity. */ + for (i = 2*deg + 1; i < num_knots - (deg+1); i++) { + if (!ts_knots_equal(knots[i], knots[i-deg])) + continue; + fst = ctrlp + (i - (deg+1)) * dim; + snd = fst + dim; + dist = ts_distance(fst, snd, dim); + if (epsilon >= 0.f && dist > epsilon) { + TS_THROW_1(try, err, status, + TS_UNDERIVABLE, + "discontinuity at knot: %f", + knots[i]) + } + memmove(snd, + snd + dim, + (num_ctrlp - (i+1-deg)) * sof_ctrlp); + memmove(&knots[i], + &knots[i+1], + (num_knots - (i+1)) * sof_real); + num_ctrlp--; + num_knots--; + i += deg-1; + } + /* Derive continuous worker. */ + for (i = 0; i < num_ctrlp-1; i++) { + for (j = 0; j < dim; j++) { + k = i *dim + j; + l = (i+1)*dim + j; + ctrlp[k] = ctrlp[l] - ctrlp[k]; + kid1 = knots[i+deg+1]; + ki1 = knots[i+1]; + span = kid1 - ki1; + if (span < TS_KNOT_EPSILON) + span = TS_KNOT_EPSILON; + ctrlp[k] *= deg; + ctrlp[k] /= span; + } + } + deg -= 1; + num_ctrlp -= 1; + num_knots -= 2; + knots += 1; + } + TS_CALL(try, err, ts_bspline_new( + num_ctrlp, dim, deg, TS_OPENED, + &swap, status)) + memcpy(ts_int_bspline_access_ctrlp(&swap), + ctrlp, + num_ctrlp * sof_ctrlp); + memcpy(ts_int_bspline_access_knots(&swap), + knots, + num_knots * sof_real); + if (spline == deriv) + ts_bspline_free(deriv); + ts_bspline_move(&swap, deriv); + TS_FINALLY + ts_bspline_free(&worker); + TS_END_TRY_RETURN(err) } - -void ts_internal_bspline_insert_knot( const tsBSpline* bspline, - const tsDeBoorNet* deBoorNet, - const size_t n, - tsBSpline* result, - jmp_buf buf - ) +tsError +ts_int_bspline_insert_knot(const tsBSpline *spline, + const tsDeBoorNet *net, + size_t n, + tsBSpline *result, + tsStatus *status) { - const size_t deg = bspline->deg; - const size_t dim = bspline->dim; - const size_t k = deBoorNet->k; - const size_t sof_f = sizeof(tsReal); - const size_t sof_c = dim * sof_f; - size_t N; /* The number of affected control points. */ - tsReal* from; /* The pointer to copy the values from. */ - tsReal* to; /* The pointer to copy the values to. */ - int stride; /* The stride of the next pointer to copy. Will be negative - * later on, thus use int. */ - size_t i; /* Used in for loops. */ + const size_t deg = ts_bspline_degree(spline); + const size_t order = ts_bspline_order(spline); + const size_t dim = ts_bspline_dimension(spline); + const tsReal knot = ts_deboornet_knot(net); + const size_t k = ts_deboornet_index(net); + const size_t mult = ts_deboornet_multiplicity(net); + const size_t sof_real = sizeof(tsReal); + const size_t sof_ctrlp = dim * sof_real; - if( deBoorNet->s + n > bspline->order ) - { - longjmp( buf, TS_MULTIPLICITY ); - } + size_t N; /**< Number of affected control points. */ + tsReal* from; /**< Pointer to copy the values from. */ + tsReal* to; /**< Pointer to copy the values to. */ + int stride; /**< Stride of the next pointer to copy. */ + size_t i; /**< Used in for loops. */ - /* Use ::ts_bspline_resize even if \n is 0 to copy - * the spline if necessary. */ - ts_internal_bspline_resize( bspline, (int) n, 1, result, buf ); + tsReal *ctrlp_spline, *ctrlp_result; + tsReal *knots_spline, *knots_result; + size_t num_ctrlp_result; + size_t num_knots_result; - if( n == 0 ) /* Nothing to insert. */ - return; + tsError err; - N = deBoorNet->h + 1; /* n > 0 implies s <= deg implies a regular evaluation - * implies h+1 is valid. */ + INIT_OUT_BSPLINE(spline, result) + if (n == 0) + return ts_bspline_copy(spline, result, status); + if (mult + n > order) { + TS_RETURN_4(status, TS_MULTIPLICITY, + "multiplicity(%f) (%lu) + %lu > order (%lu)", + knot, (unsigned long) mult, (unsigned long) n, + (unsigned long) order) + } - /* 1. Copy all necessary control points and knots from - * the original B-Spline. - * 2. Copy all necessary control points and knots from - * the de Boor net. */ + TS_CALL_ROE(err, ts_int_bspline_resize( + spline, (int)n, 1, result, status)) + ctrlp_spline = ts_int_bspline_access_ctrlp(spline); + knots_spline = ts_int_bspline_access_knots(spline); + ctrlp_result = ts_int_bspline_access_ctrlp(result); + knots_result = ts_int_bspline_access_knots(result); + num_ctrlp_result = ts_bspline_num_control_points(result); + num_knots_result = ts_bspline_num_knots(result); - /* 1. - * - * a) Copy left hand side control points from original b-spline. - * b) Copy right hand side control points from original b-spline. - * c) Copy left hand side knots from original b-spline. - * d) Copy right hand side knots form original b-spline. */ - /* copy control points */ - memmove( result->ctrlp, bspline->ctrlp, (k - deg) * sof_c ); /* a) */ - from = bspline->ctrlp + dim * (k - deg + N); - to = result->ctrlp + dim * (k - deg + N + n); /* n >= 0 implies to >= from */ - memmove( to, from, ( result->n_ctrlp - n - (k - deg + N) ) * sof_c ); /* b) */ - /* copy knots */ - memmove( result->knots, bspline->knots, (k + 1) * sof_f ); /* c) */ - from = bspline->knots + k + 1; - to = result->knots + k + 1 + n; /* n >= 0 implies to >= from */ - memmove( to, from, ( result->n_knots - n - (k + 1) ) * sof_f ); /* d) */ + /* mult + n <= deg + 1 (order) with n >= 1 + * => mult <= deg + * => regular evaluation + * => N = h + 1 is valid */ + N = ts_deboornet_num_insertions(net) + 1; - /* 2. - * - * a) Copy left hand side control points from de boor net. - * b) Copy middle part control points from de boor net. - * c) Copy right hand side control points from de boor net. - * d) Insert knots with u_k. */ - from = deBoorNet->points; - to = result->ctrlp + (k - deg) * dim; - stride = (int) (N * dim); + /* 1. Copy the relevant control points and knots from `spline'. + * 2. Copy the relevant control points and knots from `net'. */ - /* copy control points */ - for( i = 0; i < n; i++ ) /* a) */ - { - memcpy( to, from, sof_c ); - from += stride; - to += dim; - stride -= (int) dim; - } + /* 1. + * + * a) Copy left hand side control points from `spline'. + * b) Copy right hand side control points from `spline'. + * c) Copy left hand side knots from `spline'. + * d) Copy right hand side knots form `spline'. */ + /*** Copy Control Points ***/ + memmove(ctrlp_result, ctrlp_spline, (k-deg) * sof_ctrlp); /* a) */ + from = (tsReal *) ctrlp_spline + dim*(k-deg+N); + /* n >= 0 implies to >= from */ + to = ctrlp_result + dim*(k-deg+N+n); + memmove(to, from, (num_ctrlp_result-n-(k-deg+N)) * sof_ctrlp); /* b) */ + /*** Copy Knots ***/ + memmove(knots_result, knots_spline, (k+1) * sof_real); /* c) */ + from = (tsReal *) knots_spline + k+1; + /* n >= 0 implies to >= from */ + to = knots_result + k+1+n; + memmove(to, from, (num_knots_result-n-(k+1)) * sof_real); /* d) */ - memcpy( to, from, (N - n) * sof_c ); /* b) */ + /* 2. + * + * a) Copy left hand side control points from `net'. + * b) Copy middle part control points from `net'. + * c) Copy right hand side control points from `net'. + * d) Copy knot from `net' (`knot'). */ + from = ts_int_deboornet_access_points(net); + to = ctrlp_result + (k-deg)*dim; + stride = (int)(N*dim); - from -= dim; - to += (N - n) * dim; - stride = -(int) (N - n + 1) * (int) dim; /* N = h+1 with h = deg-s - * (ts_internal_bspline_evaluate) implies N = deg-s+1 = order-s. - * n <= order-s implies N-n+1 >= order-s - order-s + 1 = 1. Thus, - * -(int)(N-n+1) <= -1. */ + /*** Copy Control Points ***/ + for (i = 0; i < n; i++) { /* a) */ + memcpy(to, from, sof_ctrlp); + from += stride; + to += dim; + stride -= (int)dim; + } + memcpy(to, from, (N-n) * sof_ctrlp); /* b) */ - for( i = 0; i < n; i++ ) /* c) */ - { - memcpy( to, from, sof_c ); - from += stride; - stride -= (int) dim; - to += dim; - } + from -= dim; + to += (N-n)*dim; + /* N = h+1 with h = deg-mult (ts_int_bspline_eval) + * => N = deg-mult+1 = order-mult. + * + * n <= order-mult + * => N-n+1 >= order-mult - order-mult + 1 = 1 + * => -(int)(N-n+1) <= -1. */ + stride = -(int)(N-n+1) * (int)dim; - /* copy knots */ - to = result->knots + k + 1; - - for( i = 0; i < n; i++ ) /* d) */ - { - *to = deBoorNet->u; - to++; - } + for (i = 0; i < n; i++) { /* c) */ + memcpy(to, from, sof_ctrlp); + from += stride; + stride -= (int)dim; + to += dim; + } + /*** Copy Knot ***/ + to = knots_result + k+1; + for (i = 0; i < n; i++) { /* d) */ + *to = knot; + to++; + } + TS_RETURN_SUCCESS(status) } - -void ts_internal_bspline_evaluate( const tsBSpline* bspline, const tsReal u, - tsDeBoorNet* deBoorNet, jmp_buf buf - ) +tsError +ts_bspline_insert_knot(const tsBSpline *spline, + tsReal knot, + size_t num, + tsBSpline *result, + size_t* k, + tsStatus *status) { - const size_t deg = bspline->deg; - const size_t order = bspline->order; - const size_t dim = bspline->dim; - const size_t sof_c = dim * sizeof(tsReal); /* The size of a single - * control points.*/ - size_t k; - size_t s; - tsReal uk; /* The actual used u. */ - size_t from; /* An offset used to copy values. */ - size_t fst; /* The first affected control point, inclusive. */ - size_t lst; /* The last affected control point, inclusive. */ - size_t N; /* The number of affected control points. */ - /* the following indices are used to create the DeBoor net. */ - size_t lidx; /* The current left index. */ - size_t ridx; /* The current right index. */ - size_t tidx; /* The current to index. */ - size_t r, i, d; /* Used in for loop. */ - tsReal ui; /* The knot value at index i. */ - tsReal a, a_hat; /* The weighting factors of the control points. */ - - /* Setup the net with its default values. */ - ts_deboornet_default( deBoorNet ); - - /* 1. Find index k such that u is in between [u_k, u_k+1). - * 2. Setup already known values. - * 3. Decide by multiplicity of u how to calculate point P(u). */ - - /* 1. */ - ts_internal_bspline_find_u( bspline, u, &k, &s, buf ); - deBoorNet->k = k; - deBoorNet->s = s; - - /* 2. */ - uk = bspline->knots[k]; /* Ensures that with any */ - deBoorNet->u = ts_fequals( u, uk ) ? uk : u; /* tsReal precision the - * knot vector stays valid. */ - deBoorNet->h = deg < s ? 0 : deg - s; /* prevent underflow */ - deBoorNet->dim = dim; - - /* 3. (by 1. s <= order) - * - * 3a) Check for s = order. - * Take the two points k-s and k-s + 1. If one of - * them doesn't exist, take only the other. - * 3b) Use de boor algorithm to find point P(u). */ - if( s == order ) - { - /* only one of the two control points exists */ - if( k == deg /* only the first control point */ - || k == bspline->n_knots - 1 ) /* only the last control point */ - { - deBoorNet->points = (tsReal*) malloc( sof_c ); - - if( deBoorNet->points == NULL ) - longjmp( buf, TS_MALLOC ); - - deBoorNet->result = deBoorNet->points; - deBoorNet->n_points = 1; - from = k == deg ? 0 : (k - s) * dim; - memcpy( deBoorNet->points, bspline->ctrlp + from, sof_c ); - } - else - { - deBoorNet->points = (tsReal*) malloc( 2 * sof_c ); - - if( deBoorNet->points == NULL ) - longjmp( buf, TS_MALLOC ); - - deBoorNet->result = deBoorNet->points + dim; - deBoorNet->n_points = 2; - from = (k - s) * dim; - memcpy( deBoorNet->points, bspline->ctrlp + from, 2 * sof_c ); - } - } - else /* by 3a) s <= deg (order = deg+1) */ - { - fst = k - deg; /* by 1. k >= deg */ - lst = k - s; /* s <= deg <= k */ - N = lst - fst + 1; /* lst <= fst implies N >= 1 */ - - deBoorNet->n_points = (size_t) (N * (N + 1) * 0.5f); /* always fits */ - deBoorNet->points = (tsReal*) malloc( deBoorNet->n_points * sof_c ); - - if( deBoorNet->points == NULL ) - longjmp( buf, TS_MALLOC ); - - deBoorNet->result = deBoorNet->points + (deBoorNet->n_points - 1) * dim; - - /* copy initial values to output */ - memcpy( deBoorNet->points, bspline->ctrlp + fst * dim, N * sof_c ); - - lidx = 0; - ridx = dim; - tidx = N * dim; /* N >= 1 implies tidx > 0 */ - r = 1; - - for( ; r <= deBoorNet->h; r++ ) - { - i = fst + r; - - for( ; i <= lst; i++ ) - { - ui = bspline->knots[i]; - a = (deBoorNet->u - ui) / (bspline->knots[i + deg - r + 1] - ui); - a_hat = 1.f - a; - - for( d = 0; d < dim; d++ ) - { - deBoorNet->points[tidx++] = - a_hat * deBoorNet->points[lidx++] + - a * deBoorNet->points[ridx++]; - } - } - - lidx += dim; - ridx += dim; - } - } + tsDeBoorNet net; + tsError err; + INIT_OUT_BSPLINE(spline, result) + ts_int_deboornet_init(&net); + TS_TRY(try, err, status) + TS_CALL(try, err, ts_bspline_eval( + spline, knot, &net, status)) + TS_CALL(try, err, ts_int_bspline_insert_knot( + spline, &net, num, result, status)) + ts_deboornet_free(&net); + TS_CALL(try, err, ts_bspline_eval( + result, knot, &net, status)) + *k = ts_deboornet_index(&net); + TS_CATCH(err) + *k = 0; + TS_FINALLY + ts_deboornet_free(&net); + TS_END_TRY_RETURN(err) } - -void ts_internal_bspline_split( const tsBSpline* bspline, const tsReal u, - tsBSpline* split, size_t* k, jmp_buf buf - ) +tsError +ts_bspline_split(const tsBSpline *spline, + tsReal knot, + tsBSpline *split, + size_t* k, + tsStatus *status) { - tsDeBoorNet net; - tsError e; - jmp_buf b; - - TRY( b, e ) - ts_internal_bspline_evaluate( bspline, u, &net, b ); - - if( net.s == bspline->order ) - { - ts_internal_bspline_copy( bspline, split, b ); - *k = net.k; - } - else - { - ts_internal_bspline_insert_knot( - bspline, &net, net.h + 1, split, b ); - *k = net.k + net.h + 1; - } - - CATCH - * k = 0; - ETRY ts_deboornet_free(& net ); - - if( e < 0 ) - longjmp( buf, e ); + tsDeBoorNet net; + tsError err; + INIT_OUT_BSPLINE(spline, split) + ts_int_deboornet_init(&net); + TS_TRY(try, err, status) + TS_CALL(try, err, ts_bspline_eval( + spline, knot, &net, status)) + if (ts_deboornet_multiplicity(&net) + == ts_bspline_order(spline)) { + TS_CALL(try, err, ts_bspline_copy( + spline, split, status)) + *k = ts_deboornet_index(&net); + } else { + TS_CALL(try, err, ts_int_bspline_insert_knot( + spline, &net, + ts_deboornet_num_insertions(&net) + 1, + split, status)) + *k = ts_deboornet_index(&net) + + ts_deboornet_num_insertions(&net) + 1; + } + TS_CATCH(err) + *k = 0; + TS_FINALLY + ts_deboornet_free(&net); + TS_END_TRY_RETURN(err) } - -void ts_internal_bspline_thomas_algorithm( const tsReal* points, const size_t n, const size_t dim, - tsReal* output, jmp_buf buf - ) +tsError +ts_bspline_tension(const tsBSpline *spline, + tsReal beta, + tsBSpline *out, + tsStatus *status) { - const size_t sof_f = sizeof(tsReal); /* The size of a tsReal. */ - const size_t sof_c = dim * sof_f; /* The size of a single control point. */ - size_t len_m; /* The length m. */ - tsReal* m; /* The array of weights. */ - size_t lst; /* The index of the last control point in \points. */ - size_t i, d; /* Used in for loops. */ - size_t j, k, l; /* Used as temporary indices. */ + const size_t dim = ts_bspline_dimension(spline); + const size_t N = ts_bspline_num_control_points(spline); + const tsReal* p0 = ts_int_bspline_access_ctrlp(spline); + const tsReal* pn_1 = p0 + (N-1)*dim; - /* input validation */ - if( dim == 0 ) - longjmp( buf, TS_DIM_ZERO ); + tsReal s; /**< The straightening factor. */ + tsReal *ctrlp; /**< Pointer to the control points of `out'. */ + size_t i, d; /**< Used in for loops. */ + tsReal vec; /**< Straightening vector. */ + tsError err; - if( n == 0 ) - longjmp( buf, TS_DEG_GE_NCTRLP ); + TS_CALL_ROE(err, ts_bspline_copy(spline, out, status)) + ctrlp = ts_int_bspline_access_ctrlp(out); + if (beta < (tsReal) 0.0) beta = (tsReal) 0.0; + if (beta > (tsReal) 1.0) beta = (tsReal) 1.0; + s = 1.f - beta; - if( n <= 2 ) - { - memcpy( output, points, n * sof_c ); - return; - } - - /* In the following n >= 3 applies... */ - len_m = n - 2; /* ... len_m >= 1 */ - lst = (n - 1) * dim; /* ... lst >= 2*dim */ - - /* m_0 = 1/4, m_{k+1} = 1/(4-m_k), for k = 0,...,n-2 */ - m = (tsReal*) malloc( len_m * sof_f ); - - if( m == NULL ) - longjmp( buf, TS_MALLOC ); - - m[0] = 0.25f; - - for( i = 1; i < len_m; i++ ) - m[i] = 1.f / (4 - m[i - 1]); - - /* forward sweep */ - ts_arr_fill( output, n * dim, 0.f ); - memcpy( output, points, sof_c ); - memcpy( output + lst, points + lst, sof_c ); - - for( d = 0; d < dim; d++ ) - { - k = dim + d; - output[k] = 6 * points[k]; - output[k] -= points[d]; - } - - for( i = 2; i <= n - 2; i++ ) - { - for( d = 0; d < dim; d++ ) - { - j = (i - 1) * dim + d; - k = i * dim + d; - l = (i + 1) * dim + d; - output[k] = 6 * points[k]; - output[k] -= output[l]; - output[k] -= m[i - 2] * output[j]; /* i >= 2 */ - } - } - - /* back substitution */ - if( n > 3 ) - ts_arr_fill( output + lst, dim, 0.f ); - - for( i = n - 2; i >= 1; i-- ) - { - for( d = 0; d < dim; d++ ) - { - k = i * dim + d; - l = (i + 1) * dim + d; - /* The following line is the reason why it's important to not fill - * output with 0 if n = 3. On the other hand, if n > 3 subtracting - * 0 is exactly what we want. */ - output[k] -= output[l]; - output[k] *= m[i - 1]; /* i >= 1 */ - } - } - - if( n > 3 ) - memcpy( output + lst, points + lst, sof_c ); - - /* we are done */ - free( m ); + for (i = 0; i < N; i++) { + for (d = 0; d < dim; d++) { + vec = ((tsReal)i / (N-1)) * (pn_1[d] - p0[d]); + ctrlp[i*dim + d] = beta * ctrlp[i*dim + d] + + s * (p0[d] + vec); + } + } + TS_RETURN_SUCCESS(status) } - -void ts_internal_relaxed_uniform_cubic_bspline( const tsReal* points, - const size_t n, - const size_t dim, - tsBSpline* bspline, - jmp_buf buf - ) +tsError +ts_bspline_to_beziers(const tsBSpline *spline, + tsBSpline *beziers, + tsStatus *status) { - const size_t order = 4; /* The order of the spline to interpolate. */ - const tsReal as = 1.f / 6.f; /* The value 'a sixth'. */ - const tsReal at = 1.f / 3.f; /* The value 'a third'. */ - const tsReal tt = 2.f / 3.f; /* The value 'two third'. */ - size_t sof_c; /* The size of a single control point. */ - const tsReal* b = points; /* The array of the b values. */ - tsReal* s; /* The array of the s values. */ - size_t i, d; /* Used in for loops */ - size_t j, k, l; /* Uses as temporary indices. */ - tsError e_; - jmp_buf b_; + const size_t deg = ts_bspline_degree(spline); + const size_t order = ts_bspline_order(spline); - /* input validation */ - if( dim == 0 ) - longjmp( buf, TS_DIM_ZERO ); + int resize; /**< Number of control points to add/remove. */ + size_t k; /**< Index of the split knot value. */ + tsReal u_min; /**< Minimum of the knot values. */ + tsReal u_max; /**< Maximum of the knot values. */ - if( n <= 1 ) - longjmp( buf, TS_DEG_GE_NCTRLP ); + tsBSpline tmp; /**< Temporarily stores the result. */ + tsReal *knots; /**< Pointer to the knots of tmp. */ + size_t num_knots; /**< Number of knots in tmp. */ - /* in the following n >= 2 applies */ + tsError err; - sof_c = dim * sizeof(tsReal); /* dim > 0 implies sof_c > 0 */ + INIT_OUT_BSPLINE(spline, beziers) + TS_CALL_ROE(err, ts_bspline_copy(spline, &tmp, status)) + knots = ts_int_bspline_access_knots(&tmp); + num_knots = ts_bspline_num_knots(&tmp); - /* n >= 2 implies n-1 >= 1 implies (n-1)*4 >= 4 */ - ts_internal_bspline_new( (n - 1) * 4, dim, order - 1, TS_BEZIERS, bspline, buf ); + TS_TRY(try, err, status) + /* DO NOT FORGET TO UPDATE knots AND num_knots AFTER EACH + * TRANSFORMATION OF tmp! */ - TRY( b_, e_ ) - s = (tsReal*) malloc( n * sof_c ); + /* Fix first control point if necessary. */ + u_min = knots[deg]; + if (!ts_knots_equal(knots[0], u_min)) { + TS_CALL(try, err, ts_bspline_split( + &tmp, u_min, &tmp, &k, status)) + resize = (int)(-1*deg + (deg*2 - k)); + TS_CALL(try, err, ts_int_bspline_resize( + &tmp, resize, 0, &tmp, status)) + knots = ts_int_bspline_access_knots(&tmp); + num_knots = ts_bspline_num_knots(&tmp); + } - if( s == NULL ) - longjmp( b_, TS_MALLOC ); + /* Fix last control point if necessary. */ + u_max = knots[num_knots - order]; + if (!ts_knots_equal(knots[num_knots - 1], u_max)) { + TS_CALL(try, err, ts_bspline_split( + &tmp, u_max, &tmp, &k, status)) + num_knots = ts_bspline_num_knots(&tmp); + resize = (int)(-1*deg + (k - (num_knots - order))); + TS_CALL(try, err, ts_int_bspline_resize( + &tmp, resize, 1, &tmp, status)) + knots = ts_int_bspline_access_knots(&tmp); + num_knots = ts_bspline_num_knots(&tmp); + } - CATCH ts_bspline_free( bspline ); + /* Split internal knots. */ + k = order; + while (k < num_knots - order) { + TS_CALL(try, err, ts_bspline_split( + &tmp, knots[k], &tmp, &k, status)) + knots = ts_int_bspline_access_knots(&tmp); + num_knots = ts_bspline_num_knots(&tmp); + k++; + } - longjmp( buf, e_ ); - ETRY - /* set s_0 to b_0 and s_n = b_n */ - memcpy( s, b, sof_c ); - - memcpy( s + (n - 1) * dim, b + (n - 1) * dim, sof_c ); - - /* set s_i = 1/6*b_i + 2/3*b_{i-1} + 1/6*b_{i+1}*/ - for( i = 1; i < n - 1; i++ ) - { - for( d = 0; d < dim; d++ ) - { - j = (i - 1) * dim + d; - k = i * dim + d; - l = (i + 1) * dim + d; - s[k] = as * b[j]; - s[k] += tt * b[k]; - s[k] += as * b[l]; - } - } - - /* create beziers from b and s */ - for( i = 0; i < n - 1; i++ ) - { - for( d = 0; d < dim; d++ ) - { - j = i * dim + d; - k = i * 4 * dim + d; - l = (i + 1) * dim + d; - bspline->ctrlp[k] = s[j]; - bspline->ctrlp[k + dim] = tt * b[j] + at * b[l]; - bspline->ctrlp[k + 2 * dim] = at * b[j] + tt * b[l]; - bspline->ctrlp[k + 3 * dim] = s[l]; - } - } - - free( s ); + if (spline == beziers) + ts_bspline_free(beziers); + ts_bspline_move(&tmp, beziers); + TS_FINALLY + ts_bspline_free(&tmp); + TS_END_TRY_RETURN(err) } - -void ts_internal_bspline_interpolate_cubic( const tsReal* points, const size_t n, const size_t dim, - tsBSpline* bspline, jmp_buf buf - ) +tsError +ts_bspline_elevate_degree(const tsBSpline *spline, + size_t amount, + tsReal epsilon, + tsBSpline *elevated, + tsStatus * status) { - tsError e; - jmp_buf b; - tsReal* thomas = (tsReal*) malloc( n * dim * sizeof(tsReal) ); + tsBSpline worker; + size_t dim, order; + tsReal *ctrlp, *knots; + size_t num_beziers, i, a, c, d, offset, idx; + tsReal f, f_hat, *first, *last; + tsError err; - if( thomas == NULL ) - longjmp( buf, TS_MALLOC ); + /* Trivial case. */ + if (amount == 0) + return ts_bspline_copy(spline, elevated, status); - TRY( b, e ) - ts_internal_bspline_thomas_algorithm( points, n, dim, thomas, b ); - ts_internal_relaxed_uniform_cubic_bspline( thomas, n, dim, bspline, b ); - ETRY free( thomas ); + /* An overview of this algorithm can be found at: + * https://pages.mtu.edu/~shene/COURSES/cs3621/LAB/curve/elevation.html */ + INIT_OUT_BSPLINE(spline, elevated); + worker = ts_bspline_init(); + TS_TRY(try, err, status) + /* Decompose `spline' into a sequence of bezier curves and make + * space for the additional control points and knots that are + * to be inserted. Results are stored in `worker'. */ + TS_CALL(try, err, ts_bspline_to_beziers( + spline, &worker, status)); + num_beziers = ts_bspline_num_control_points(&worker) / + ts_bspline_order(&worker); + TS_CALL(try, err, ts_int_bspline_resize( + /* Resize by the number of knots to be inserted. Note + * that this creates too many control points (due to + * increasing the degree), which are removed at the end + * of this function. */ + &worker, (int) ((num_beziers+1) * amount), 1, &worker, + status)); + dim = ts_bspline_dimension(&worker); + order = ts_bspline_order(&worker); + ctrlp = ts_int_bspline_access_ctrlp(&worker); + knots = ts_int_bspline_access_knots(&worker); - if( e < 0 ) - longjmp( buf, e ); + /* Move all but the first bezier curve to their new location in + * the control point array so that the additional control + * points can be inserted without overwriting the others. Note + * that iteration must run backwards. Otherwise, the moved + * values overwrite each other. */ + for (i = num_beziers - 1; i > 0; i--) { + /* `i' can be interpreted as the number of bezier + * curves before the current bezier curve. */ + + /* Location of current bezier curve. */ + offset = i * order * dim; + /* Each elevation inserts an additional control point + * into every bezier curve. `i * amount' is the total + * number of control points to be inserted before the + * current bezier curve. */ + memmove(ctrlp + offset + (i * amount * dim), + ctrlp + offset, + dim * order * sizeof(tsReal)); + } + + /* Move all but the first group of knots to their new location + * in the knot vector so that the additional knots can be + * inserted without overwriting the others. Note that iteration + * must run backwards. Otherwise, the moved values overwrite + * each other. */ + for (i = num_beziers; i > 0; i--) { + /* Note that the number of knot groups is one more than + * the number of bezier curves. `i' can be interpreted + * as the number of knot groups before the current + * group. */ + + /* Location of current knot group. */ + offset = i * order; + /* Each elevation inserts an additional knot into every + * group of knots. `i * amount' is the total number of + * knots to be inserted before the current knot + * group. */ + memmove(knots + offset + (i * amount), + knots + offset, + order * sizeof(tsReal)); + } + + /* `worker' is now fully set up. + * The following formulas are based on: + * https://pages.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/bezier-elev.html */ + for (a = 0; a < amount; a++) { + /* For each bezier curve... */ + for (i = 0; i < num_beziers; i++) { + /* ... 1) Insert and update control points. */ + + /* Location of current bezier curve. Each + * elevation (`a') inserts an additional + * control point into every bezier curve and + * increases the degree (`order') by one. The + * location is thus made up of two parts: + * + * i) `i * order', which is the location taking + * into account the increasing order but + * neglecting the control points that are to be + * inserted before the current bezier curve. It + * can be seen as some sort of base location: + * Where would the bezier curve be (with + * respect to the current value of `order') if + * no additional control points had to be + * inserted? + * + * ii) `i * (amount - a)', which is the total + * number of control points to be inserted + * before the current bezier curve + * (`i * amount') taking into account the + * increasing order (`order' and `a' are + * increased equally, thus, `a' compensates for + * the increasing value of `order'). This part + * adds the necessary offset to the base + * location (`i * order'). */ + offset = (i * order + i * (amount - a)) * dim; + /* Duplicate last control point to the new end + * position (next control point). */ + memmove(ctrlp + offset + ((order) * dim), + ctrlp + offset + ((order-1) * dim), + dim * sizeof(tsReal)); + /* All but the outer control points must be + * recalculated (domain: [1, order - 1]). By + * traversing backwards, control points can be + * modified in-place. */ + for (c = order - 1; c > 0; c--) { + /* Location of current control point + * within current bezier curve. */ + idx = offset + c * dim; + f = (tsReal) c / (tsReal) (order); + f_hat = 1 - f; + for (d = 0; d < dim; d++) { + /* For the sake of space, we + * increment idx by d and + * decrement it at the end of + * this loop. */ + idx += d; + ctrlp[idx] = + f * ctrlp[idx - dim] + + f_hat * ctrlp[idx]; + /* Reset idx. */ + idx -= d; + } + } + + /* ...2) Increase the multiplicity of the + * second knot group (maximum of the domain of + * the current bezier curve) by one. Note that + * this loop misses the last knot group (the + * group of the last bezier curve) as there is + * one more knot group than bezier curves to + * process. Thus, the last group must be + * increased separately after this loop. */ + + /* Location of current knot group. Each + * elevation (`a') inserts an additional + * knot into the knot vector of every bezier + * curve and increases the degree (`order') by + * one. The location is thus made up of two + * parts: + * + * i) `i * order', which is the location taking + * into account the increasing order but + * neglecting the knots that are to be inserted + * before the current knot group. It can be + * seen as some sort of base location: Where + * would the knot group be (with respect to the + * current value of `order') if no additional + * knots had to be inserted? + * + * ii) `i * (amount - a)', which is the total + * number of knots to be inserted before the + * current knot group (`i * amount') taking + * into account the increasing order (`order' + * and `a' are increased equally, thus, `a' + * compensates for the increasing value of + * `order'). This part adds the necessary + * offset to the base location + * (`i * order'). */ + offset = i * order + i * (amount - a); + /* Duplicate knot. */ + knots[offset + order] = knots[offset]; + } + + /* Increase the multiplicity of the very last knot + * group (the second group of the last bezier curve) + * by one. For more details, see knot duplication in + * previous loop. */ + offset = num_beziers * order + + num_beziers * (amount - a); + knots[offset + order] = knots[offset]; + + /* Elevated by one. */ + order++; + } + + /* Combine bezier curves. */ + d = 0; /* Number of removed knots/control points. */ + for (i = 0; i < num_beziers - 1; i++) { + /* Is the last control point of bezier curve `i' equal + * to the first control point of bezier curve `i+1'? */ + last = ctrlp + ( + i * order /* base location of `i' */ + - d /* minus the number of removed values */ + + (order - 1) /* jump to last control point */ + ) * dim; + first = last + dim; /* next control point */ + if (ts_distance(last, first, dim) <= epsilon) { + /* Move control points. */ + memmove(last, first, (num_beziers - 1 - i) * + order * dim * sizeof(tsReal)); + + /* Move knots. `last' is the last knot of the + * second knot group of bezier curve `i'. + * `first' is the first knot of the first knot + * group of bezier curve `i+1'. The + * calculations are quite similar to those for + * the control points `last' and `first' (see + * above). */ + last = knots + i * order - d + (2 * order - 1); + first = last + 1; + memmove(last, first, (num_beziers - 1 - i) * + order * sizeof(tsReal)); + + /* Removed one knot/control point. */ + d++; + } + } + + /* Repair internal state. */ + worker.pImpl->deg = order - 1; + worker.pImpl->n_knots -= d; + worker.pImpl->n_ctrlp = ts_bspline_num_knots(&worker) - order; + memmove(ts_int_bspline_access_knots(&worker), + knots, ts_bspline_sof_knots(&worker)); + worker.pImpl = realloc(worker.pImpl, + ts_int_bspline_sof_state(&worker)); + if (worker.pImpl == NULL) { + TS_THROW_0(try, err, status, TS_MALLOC, + "out of memory") + } + + /* Move `worker' to output parameter. */ + if (spline == elevated) + ts_bspline_free(elevated); + ts_bspline_move(&worker, elevated); + TS_FINALLY + ts_bspline_free(&worker); + TS_END_TRY_RETURN(err) } - -void ts_internal_bspline_derive( const tsBSpline* original, - tsBSpline* derivative, jmp_buf buf - ) +tsError +ts_bspline_align(const tsBSpline *s1, + const tsBSpline *s2, + tsReal epsilon, + tsBSpline *s1_out, + tsBSpline *s2_out, + tsStatus *status) { - const size_t sof_f = sizeof(tsReal); - const size_t dim = original->dim; - const size_t deg = original->deg; - const size_t nc = original->n_ctrlp; - const size_t nk = original->n_knots; - tsReal* from_ctrlp = original->ctrlp; - tsReal* from_knots = original->knots; - tsReal* to_ctrlp = NULL; - tsReal* to_knots = NULL; - size_t i, j, k; + tsBSpline s1_worker, s2_worker, *smaller, *larger; + tsDeBoorNet net; /* the net of `smaller'. */ + size_t i, missing, remaining; + tsReal min, max, shift, nextKnot; + tsError err; - if( deg < 1 || nc < 2 ) - longjmp( buf, TS_UNDERIVABLE ); + INIT_OUT_BSPLINE(s1, s1_out) + INIT_OUT_BSPLINE(s2, s2_out) + s1_worker = ts_bspline_init(); + s2_worker = ts_bspline_init(); + smaller = larger = NULL; + TS_TRY(try, err, status) + /* Set up `s1_worker' and `s2_worker'. After this + * if-elseif-else-block, `s1_worker' and `s2_worker' have same + * degree. */ + if (ts_bspline_degree(s1) < ts_bspline_degree(s2)) { + TS_CALL(try, err, ts_bspline_elevate_degree(s1, + ts_bspline_degree(s2) - ts_bspline_degree(s1), + epsilon, &s1_worker, status)) + TS_CALL(try, err, ts_bspline_copy( + s2, &s2_worker, status)) + } else if (ts_bspline_degree(s2) < ts_bspline_degree(s1)) { + TS_CALL(try, err, ts_bspline_elevate_degree(s2, + ts_bspline_degree(s1) - ts_bspline_degree(s2), + epsilon, &s2_worker, status)) + TS_CALL(try, err, ts_bspline_copy( + s1, &s1_worker, status)) + } else { + TS_CALL(try, err, ts_bspline_copy( + s1, &s1_worker, status)) + TS_CALL(try, err, ts_bspline_copy( + s2, &s2_worker, status)) + } - if( original != derivative ) - { - ts_internal_bspline_new( nc - 1, dim, deg - 1, TS_NONE, derivative, buf ); - to_ctrlp = derivative->ctrlp; - to_knots = derivative->knots; - } - else - { - to_ctrlp = (tsReal*) malloc( ( (nc - 1) * dim + (nk - 2) ) * sof_f ); + /* Set up `smaller', `larger', and `net'. */ + if (ts_bspline_num_knots(&s1_worker) < + ts_bspline_num_knots(&s2_worker)) { + smaller = &s1_worker; + larger = &s2_worker; + } else { + smaller = &s2_worker; + larger = &s1_worker; + } + TS_CALL(try, err, ts_int_deboornet_new( + smaller, &net, status)) - if( to_ctrlp == NULL ) - longjmp( buf, TS_MALLOC ); + /* Insert knots into `smaller' until it has the same number of + * knots (and therefore the same number of control points) as + * `larger'. */ + ts_bspline_domain(smaller, &min, &max); + missing = remaining = ts_bspline_num_knots(larger) - + ts_bspline_num_knots(smaller); + shift = (tsReal) 0.0; + if (missing > 0) + shift = ( (tsReal) 1.0 / missing ) * (tsReal) 0.5; + for (i = 0; remaining > 0; i++, remaining--) { + nextKnot = (max - min) * ((tsReal)i / missing) + min; + nextKnot += shift; + TS_CALL(try, err, ts_int_bspline_eval_woa( + smaller, nextKnot, &net, status)) + while (!ts_deboornet_num_insertions(&net)) { + /* Linear exploration for next knot. */ + nextKnot += 5 * TS_KNOT_EPSILON; + if (nextKnot > max) { + TS_THROW_0(try, err, status, + TS_NO_RESULT, + "no more knots for insertion") + } + TS_CALL(try, err, ts_int_bspline_eval_woa( + smaller, nextKnot, &net, status)) + } + TS_CALL(try, err, ts_int_bspline_insert_knot( + smaller, &net, 1, smaller, status)) + } - to_knots = to_ctrlp + (nc - 1) * dim; - } - - for( i = 0; i < nc - 1; i++ ) - { - for( j = 0; j < dim; j++ ) - { - if( ts_fequals( from_knots[i + deg + 1], from_knots[i + 1] ) ) - { - free( to_ctrlp ); - longjmp( buf, TS_UNDERIVABLE ); - } - else - { - k = i * dim + j; - to_ctrlp[k] = from_ctrlp[(i + 1) * dim + j] - from_ctrlp[k]; - to_ctrlp[k] *= deg; - to_ctrlp[k] /= from_knots[i + deg + 1] - from_knots[i + 1]; - } - } - } - - memcpy( to_knots, from_knots + 1, (nk - 2) * sof_f ); - - if( original == derivative ) - { - /* free old memory */ - free( from_ctrlp ); - /* assign new values */ - derivative->deg = deg - 1; - derivative->order = deg; - derivative->n_ctrlp = nc - 1; - derivative->n_knots = nk - 2; - derivative->ctrlp = to_ctrlp; - derivative->knots = to_knots; - } + if (s1 == s1_out) + ts_bspline_free(s1_out); + if (s2 == s2_out) + ts_bspline_free(s2_out); + ts_bspline_move(&s1_worker, s1_out); + /* if `s1_out' == `s2_out', `s2_worker' must not be moved + * because otherwise the memory of `s1_worker' is leaked + * (`s2_worker' overrides `s1_worker'). */ + if (s1_out != s2_out) + ts_bspline_move(&s2_worker, s2_out); + TS_FINALLY + ts_bspline_free(&s1_worker); + ts_bspline_free(&s2_worker); + ts_deboornet_free(&net); + TS_END_TRY_RETURN(err) } - -void ts_internal_bspline_buckle( const tsBSpline* bspline, const tsReal b, - tsBSpline* buckled, jmp_buf buf - ) +tsError +ts_bspline_morph(const tsBSpline *origin, + const tsBSpline *target, + tsReal t, + tsReal epsilon, + tsBSpline *out, + tsStatus *status) { - const tsReal b_hat = 1.f - b; /* The straightening factor. */ - const size_t dim = bspline->dim; - const size_t N = bspline->n_ctrlp; - const tsReal* p0 = bspline->ctrlp; /* Pointer to first ctrlp. */ - const tsReal* pn_1 = p0 + (N - 1) * dim; /* Pointer to the last ctrlp. */ - size_t i, d; /* Used in for loops. */ + tsBSpline origin_al, target_al; /* aligned origin and target */ + tsReal *origin_al_c, *origin_al_k; /* control points and knots */ + tsReal *target_al_c, *target_al_k; /* control points and knots */ - ts_internal_bspline_copy( bspline, buckled, buf ); + /* Properties of `out'. */ + size_t deg, dim, num_ctrlp, num_knots; + tsReal *ctrlp, *knots; + tsBSpline tmp; /* temporary buffer if `out' must be resized */ - for( i = 0; i < N; i++ ) - { - for( d = 0; d < dim; d++ ) - { - buckled->ctrlp[i * dim + d] = - b * buckled->ctrlp[i * dim + d] + - b_hat * ( p0[d] + ( (tsReal) i / (N - 1) ) * (pn_1[d] - p0[d]) ); - } - } + tsReal t_hat; + size_t i, offset, d; + tsError err; + + origin_al = ts_bspline_init(); + target_al = ts_bspline_init(); + TS_TRY(try, err, status) + /* Clamp `t' to domain [0, 1] and set up `t_hat'. */ + if (t < (tsReal) 0.0) t = (tsReal) 0.0; + if (t > (tsReal) 1.0) t = (tsReal) 1.0; + t_hat = (tsReal) 1.0 - t; + + /* Set up `origin_al' and `target_al'. */ + /* Degree must be elevated... */ + if (ts_bspline_degree(origin) != ts_bspline_degree(target) || + /* .. or knots (and thus control points) must be inserted. */ + ts_bspline_num_knots(origin) != ts_bspline_num_knots(target)) { + TS_CALL(try, err, ts_bspline_align( + origin, target, epsilon, &origin_al, &target_al, + status)); + } else { + /* Flat copy. */ + origin_al = *origin; + target_al = *target; + } + origin_al_c = ts_int_bspline_access_ctrlp(&origin_al); + origin_al_k = ts_int_bspline_access_knots(&origin_al); + target_al_c = ts_int_bspline_access_ctrlp(&target_al); + target_al_k = ts_int_bspline_access_knots(&target_al); + + /* Set up `out'. */ + deg = ts_bspline_degree(&origin_al); + num_ctrlp = ts_bspline_num_control_points(&origin_al); + dim = ts_bspline_dimension(&origin_al); + if (ts_bspline_dimension(&target_al) < dim) + dim = ts_bspline_dimension(&target_al); + if (out->pImpl == NULL) { + TS_CALL(try, err, ts_bspline_new(num_ctrlp, dim, deg, + TS_OPENED /* doesn't matter */, out, status)) + } else if (ts_bspline_degree(out) != deg || + ts_bspline_num_control_points(out) != num_ctrlp || + ts_bspline_dimension(out) != dim) { + TS_CALL(try, err, ts_bspline_new(num_ctrlp, dim, deg, + TS_OPENED /* doesn't matter */, &tmp, status)) + ts_bspline_free(out); + ts_bspline_move(&tmp, out); + } + num_knots = ts_bspline_num_knots(out); + ctrlp = ts_int_bspline_access_ctrlp(out); + knots = ts_int_bspline_access_knots(out); + + /* Interpolate control points. */ + for (i = 0; i < num_ctrlp; i++) { + for (d = 0; d < dim; d++) { + offset = i * dim + d; + ctrlp[offset] = t * target_al_c[offset] + + t_hat * origin_al_c[offset]; + } + } + + /* Interpolate knots. */ + for (i = 0; i < num_knots; i++) { + knots[i] = t * target_al_k[i] + + t_hat * origin_al_k[i]; + } + TS_FINALLY + if (origin->pImpl != origin_al.pImpl) + ts_bspline_free(&origin_al); + if (target->pImpl != target_al.pImpl) + ts_bspline_free(&target_al); + TS_END_TRY_RETURN(err) +} +/*! @} */ + + + +/*! @name Serialization and Persistence + * + * @{ + */ +tsError +ts_int_bspline_to_json(const tsBSpline *spline, + JSON_Value **value, + tsStatus *status) +{ + const size_t deg = ts_bspline_degree(spline); + const size_t dim = ts_bspline_dimension(spline); + const size_t len_ctrlp = ts_bspline_len_control_points(spline); + const size_t len_knots = ts_bspline_num_knots(spline); + const tsReal *ctrlp = ts_int_bspline_access_ctrlp(spline); + const tsReal *knots = ts_int_bspline_access_knots(spline); + + size_t i; /**< Used in loops */ + tsError err; + + JSON_Value *ctrlp_value; + JSON_Value *knots_value; + JSON_Object *spline_object; + JSON_Array *ctrlp_array; + JSON_Array *knots_array; + + *value = ctrlp_value = knots_value = NULL; + TS_TRY(values, err, status) + /* Init memory. */ + *value = json_value_init_object(); + if (!*value) { + TS_THROW_0(values, err, status, TS_MALLOC, + "out of memory") + } + ctrlp_value = json_value_init_array(); + if (!ctrlp_value) { + TS_THROW_0(values, err, status, TS_MALLOC, + "out of memory") + } + knots_value = json_value_init_array(); + if (!knots_value) { + TS_THROW_0(values, err, status, TS_MALLOC, + "out of memory") + } + + /* Although the following functions cannot fail, that is, they + * won't return NULL or JSONFailure, we nevertheless handle + * unexpected return values. */ + + /* Init output. */ + spline_object = json_value_get_object(*value); + if (!spline_object) { + TS_THROW_0(values, err, status, TS_MALLOC, + "out of memory") + } + + /* Set degree and dimension. */ + if (JSONSuccess != json_object_set_number(spline_object, + "degree", + (double) deg)) { + TS_THROW_0(values, err, status, TS_MALLOC, + "out of memory") + } + if (JSONSuccess != json_object_set_number(spline_object, + "dimension", + (double) dim)) { + TS_THROW_0(values, err, status, TS_MALLOC, + "out of memory") + } + + /* Set control points. */ + if (JSONSuccess != json_object_set_value(spline_object, + "control_points", + ctrlp_value)) { + TS_THROW_0(values, err, status, TS_MALLOC, + "out of memory") + } + ctrlp_array = json_array(ctrlp_value); + if (!ctrlp_array) { + TS_THROW_0(values, err, status, TS_MALLOC, + "out of memory") + } + for (i = 0; i < len_ctrlp; i++) { + if (JSONSuccess != json_array_append_number( + ctrlp_array, (double) ctrlp[i])) { + TS_THROW_0(values, err, status, TS_MALLOC, + "out of memory") + } + } + + /* Set knots. */ + if (JSONSuccess != json_object_set_value(spline_object, + "knots", + knots_value)) { + TS_THROW_0(values, err, status, TS_MALLOC, + "out of memory") + } + knots_array = json_array(knots_value); + if (!knots_array) { + TS_THROW_0(values, err, status, TS_MALLOC, + "out of memory") + } + for (i = 0; i < len_knots; i++) { + if (JSONSuccess != json_array_append_number( + knots_array, (double) knots[i])) { + TS_THROW_0(values, err, status, TS_MALLOC, + "out of memory") + } + } + TS_CATCH(err) + if (*value) + json_value_free(*value); + if (ctrlp_value && !json_value_get_parent(ctrlp_value)) + json_value_free(ctrlp_value); + if (knots_value && !json_value_get_parent(knots_value)) + json_value_free(knots_value); + *value = NULL; + TS_END_TRY_RETURN(err) } - -void ts_internal_bspline_to_beziers( const tsBSpline* bspline, - tsBSpline* beziers, jmp_buf buf - ) +tsError +ts_int_bspline_parse_json(const JSON_Value *spline_value, + tsBSpline *spline, + tsStatus *status) { - tsError e; - jmp_buf b; - const size_t deg = bspline->deg; - const size_t order = bspline->order; - tsBSpline tmp; - int resize; /* The number of control points to add/remove. */ - size_t k; /* The index of the splitted knot value. */ - tsReal u_min; /* The minimum of the knot values. */ - tsReal u_max; /* The maximum of the knot values. */ + size_t deg, dim, len_ctrlp, num_knots; + tsReal *ctrlp, *knots; - ts_internal_bspline_copy( bspline, &tmp, buf ); + JSON_Object *spline_object; + JSON_Value *deg_value; + JSON_Value *dim_value; + JSON_Value *ctrlp_value; + JSON_Array *ctrlp_array; + JSON_Value *knots_value; + JSON_Array *knots_array; + JSON_Value *real_value; + size_t i; + tsError err; - TRY( b, e ) - /* fix first control point if necessary */ - u_min = tmp.knots[deg]; + ts_int_bspline_init(spline); - if( !ts_fequals( tmp.knots[0], u_min ) ) - { - ts_internal_bspline_split( &tmp, u_min, &tmp, &k, b ); - resize = (int) ( -1 * deg + (deg * 2 - k) ); - ts_internal_bspline_resize( &tmp, resize, 0, &tmp, b ); - } + /* Read spline object. */ + if (json_value_get_type(spline_value) != JSONObject) + TS_RETURN_0(status, TS_PARSE_ERROR, "invalid json input") + spline_object = json_value_get_object(spline_value); + if (!spline_object) + TS_RETURN_0(status, TS_PARSE_ERROR, "invalid json input") - /* fix last control point if necessary */ - u_max = tmp.knots[tmp.n_knots - order]; + /* Read degree. */ + deg_value = json_object_get_value(spline_object, "degree"); + if (json_value_get_type(deg_value) != JSONNumber) + TS_RETURN_0(status, TS_PARSE_ERROR, "degree is not a number") + if (json_value_get_number(deg_value) < -0.01f) { + TS_RETURN_1(status, TS_PARSE_ERROR, + "degree (%f) < 0", + json_value_get_number(deg_value)) + } + deg = (size_t) json_value_get_number(deg_value); - if( !ts_fequals( tmp.knots[tmp.n_knots - 1], u_max ) ) - { - ts_internal_bspline_split( &tmp, u_max, &tmp, &k, b ); - resize = (int) ( -1 * deg + ( k - (tmp.n_knots - order) ) ); - ts_internal_bspline_resize( &tmp, resize, 1, &tmp, b ); - } + /* Read dimension. */ + dim_value = json_object_get_value(spline_object, "dimension"); + if (json_value_get_type(dim_value) != JSONNumber) { + TS_RETURN_0(status, TS_PARSE_ERROR, + "dimension is not a number") + } + if (json_value_get_number(dim_value) < 0.99f) { + TS_RETURN_1(status, TS_PARSE_ERROR, + "dimension (%f) < 1", + json_value_get_number(deg_value)) + } + dim = (size_t) json_value_get_number(dim_value); - k = order; + /* Read length of control point vector. */ + ctrlp_value = json_object_get_value(spline_object, "control_points"); + if (json_value_get_type(ctrlp_value) != JSONArray) { + TS_RETURN_0(status, TS_PARSE_ERROR, + "control_points is not an array") + } + ctrlp_array = json_value_get_array(ctrlp_value); + len_ctrlp = json_array_get_count(ctrlp_array); + if (len_ctrlp % dim != 0) { + TS_RETURN_2(status, TS_PARSE_ERROR, + "len(control_points) (%lu) %% dimension (%lu) != 0", + (unsigned long) len_ctrlp, (unsigned long) dim) + } - while( k < tmp.n_knots - order ) - { - ts_internal_bspline_split( &tmp, tmp.knots[k], &tmp, &k, b ); - k++; - } + /* Read number of knots. */ + knots_value = json_object_get_value(spline_object, "knots"); + if (json_value_get_type(knots_value) != JSONArray) { + TS_RETURN_0(status, TS_PARSE_ERROR, + "knots is not an array") + } + knots_array = json_value_get_array(knots_value); + num_knots = json_array_get_count(knots_array); - if( bspline == beziers ) - ts_bspline_free( beziers ); + /* Create spline. */ + TS_TRY(try, err, status) + TS_CALL(try, err, ts_bspline_new( + len_ctrlp/dim, dim, deg, + TS_CLAMPED, spline, status)) + if (num_knots != ts_bspline_num_knots(spline)) + TS_THROW_2(try, err, status, TS_NUM_KNOTS, + "unexpected num(knots): (%lu) != (%lu)", + (unsigned long) num_knots, + (unsigned long) ts_bspline_num_knots(spline)) - ts_bspline_move( &tmp, beziers ); - ETRY ts_bspline_free(& tmp ); + /* Set control points. */ + ctrlp = ts_int_bspline_access_ctrlp(spline); + for (i = 0; i < len_ctrlp; i++) { + real_value = json_array_get_value(ctrlp_array, i); + if (json_value_get_type(real_value) != JSONNumber) + TS_THROW_1(try, err, status, TS_PARSE_ERROR, + "control_points: value at index %lu is not a number", + (unsigned long) i) + ctrlp[i] = (tsReal) json_value_get_number(real_value); + } + TS_CALL(try, err, ts_bspline_set_control_points( + spline, ctrlp, status)) - if( e < 0 ) - longjmp( buf, e ); + /* Set knots. */ + knots = ts_int_bspline_access_knots(spline); + for (i = 0; i < num_knots; i++) { + real_value = json_array_get_value(knots_array, i); + if (json_value_get_type(real_value) != JSONNumber) + TS_THROW_1(try, err, status, TS_PARSE_ERROR, + "knots: value at index %lu is not a number", + (unsigned long) i) + knots[i] = (tsReal) json_value_get_number(real_value); + } + TS_CALL(try, err, ts_bspline_set_knots( + spline, knots, status)) + TS_CATCH(err) + ts_bspline_free(spline); + TS_END_TRY_RETURN(err) } - -void ts_internal_bspline_set_ctrlp( const tsBSpline* bspline, const tsReal* ctrlp, - tsBSpline* result, jmp_buf buf - ) +tsError +ts_bspline_to_json(const tsBSpline *spline, + char **json, + tsStatus *status) { - const size_t s = bspline->n_ctrlp * bspline->dim * sizeof(tsReal); - - ts_internal_bspline_copy( bspline, result, buf ); - memmove( result->ctrlp, ctrlp, s ); + tsError err; + JSON_Value *value = NULL; + *json = NULL; + TS_CALL_ROE(err, ts_int_bspline_to_json(spline, &value, status)) + *json = json_serialize_to_string_pretty(value); + json_value_free(value); + if (!*json) + TS_RETURN_0(status, TS_MALLOC, "out of memory") + TS_RETURN_SUCCESS(status) } - -void ts_internal_bspline_set_knots( const tsBSpline* bspline, const tsReal* knots, - tsBSpline* result, jmp_buf buf - ) +tsError +ts_bspline_parse_json(const char *json, + tsBSpline *spline, + tsStatus *status) { - const size_t s = bspline->n_knots * sizeof(tsReal); - - ts_internal_bspline_copy( bspline, result, buf ); - memmove( result->knots, knots, s ); + tsError err; + JSON_Value *value = NULL; + ts_int_bspline_init(spline); + TS_TRY(try, err, status) + value = json_parse_string(json); + if (!value) { + TS_RETURN_0(status, TS_PARSE_ERROR, + "invalid json input") + } + TS_CALL(try, err, ts_int_bspline_parse_json( + value, spline, status)) + TS_FINALLY + if (value) + json_value_free(value); + TS_END_TRY_RETURN(err) } - -/******************************************************** -* * -* Interface implementation * -* * -********************************************************/ -void ts_deboornet_default( tsDeBoorNet* deBoorNet ) +tsError +ts_bspline_save(const tsBSpline *spline, + const char *path, + tsStatus *status) { - deBoorNet->u = 0.f; - deBoorNet->k = 0; - deBoorNet->s = 0; - deBoorNet->h = 0; - deBoorNet->dim = 0; - deBoorNet->n_points = 0; - deBoorNet->points = NULL; - deBoorNet->result = NULL; + tsError err; + JSON_Status json_status; + JSON_Value *value = NULL; + TS_CALL_ROE(err, ts_int_bspline_to_json(spline, &value, status)) + json_status = json_serialize_to_file_pretty(value, path); + json_value_free(value); + if (json_status != JSONSuccess) + TS_RETURN_0(status, TS_IO_ERROR, "unexpected io error") + TS_RETURN_SUCCESS(status) } - -void ts_deboornet_move( tsDeBoorNet* from, tsDeBoorNet* to ) +tsError +ts_bspline_load(const char *path, + tsBSpline *spline, + tsStatus *status) { - if( from == to ) - return; + tsError err; + FILE *file = NULL; + JSON_Value *value = NULL; + ts_int_bspline_init(spline); + TS_TRY(try, err, status) + file = fopen(path, "r"); + if (!file) { + TS_THROW_0(try, err, status, TS_IO_ERROR, + "unable to open file") + } + value = json_parse_file(path); + if (!value) { + TS_RETURN_0(status, TS_PARSE_ERROR, + "invalid json input") + } + TS_CALL(try, err, ts_int_bspline_parse_json( + value, spline, status)) + TS_FINALLY + if (file) + fclose(file); + if (value) + json_value_free(value); + TS_CATCH(err) + ts_bspline_free(spline); + TS_END_TRY_RETURN(err) +} +/*! @} */ - to->u = from->u; - to->k = from->k; - to->s = from->s; - to->h = from->h; - to->dim = from->dim; - to->n_points = from->n_points; - to->points = from->points; - to->result = from->result; - ts_deboornet_default( from ); + + +/*! @name Vector Math + * @{ + */ +void +ts_vec2_init(tsReal *out, + tsReal x, + tsReal y) +{ + out[0] = x; + out[1] = y; } - -void ts_deboornet_free( tsDeBoorNet* deBoorNet ) +void +ts_vec3_init(tsReal *out, + tsReal x, + tsReal y, + tsReal z) { - if( deBoorNet->points != NULL ) - free( deBoorNet->points ); /* automatically frees the field result */ - - ts_deboornet_default( deBoorNet ); + out[0] = x; + out[1] = y; + out[2] = z; } - -void ts_bspline_default( tsBSpline* bspline ) +void +ts_vec4_init(tsReal *out, + tsReal x, + tsReal y, + tsReal z, + tsReal w) { - bspline->deg = 0; - bspline->order = 0; - bspline->dim = 0; - bspline->n_ctrlp = 0; - bspline->n_knots = 0; - bspline->ctrlp = NULL; - bspline->knots = NULL; + out[0] = x; + out[1] = y; + out[2] = z; + out[3] = w; } - -void ts_bspline_free( tsBSpline* bspline ) +void +ts_vec2_set(tsReal *out, + const tsReal *x, + size_t dim) { - if( bspline->ctrlp != NULL ) - free( bspline->ctrlp ); - - ts_bspline_default( bspline ); + const size_t n = dim > 2 ? 2 : dim; + memmove(out, x, n * sizeof(tsReal)); + if (dim < 2) + ts_arr_fill(out + dim, 2 - dim, (tsReal) 0.0); } - -void ts_bspline_move( tsBSpline* from, tsBSpline* to ) +void +ts_vec3_set(tsReal *out, + const tsReal *x, + size_t dim) { - if( from == to ) - return; - - to->deg = from->deg; - to->order = from->order; - to->dim = from->dim; - to->n_ctrlp = from->n_ctrlp; - to->n_knots = from->n_knots; - to->ctrlp = from->ctrlp; - to->knots = from->knots; - ts_bspline_default( from ); + const size_t n = dim > 3 ? 3 : dim; + memmove(out, x, n * sizeof(tsReal)); + if (dim < 3) + ts_arr_fill(out + dim, 3 - dim, (tsReal) 0.0); } - -tsError ts_bspline_new( size_t n_ctrlp, size_t dim, size_t deg, tsBSplineType type, - tsBSpline* bspline - ) +void +ts_vec4_set(tsReal *out, + const tsReal *x, + size_t dim) { - tsError err; - jmp_buf buf; - - TRY( buf, err ) - ts_internal_bspline_new( n_ctrlp, dim, deg, type, bspline, buf ); - CATCH ts_bspline_default( bspline ); - - ETRY - return err; + const size_t n = dim > 4 ? 4 : dim; + memmove(out, x, n * sizeof(tsReal)); + if (dim < 4) + ts_arr_fill(out + dim, 4 - dim, (tsReal) 0.0); } - -tsError ts_bspline_interpolate_cubic( const tsReal* points, size_t n, size_t dim, - tsBSpline* bspline - ) +void +ts_vec_add(const tsReal *x, + const tsReal *y, + size_t dim, + tsReal *out) { - tsError err; - jmp_buf buf; - - TRY( buf, err ) - ts_internal_bspline_interpolate_cubic( points, n, dim, bspline, buf ); - CATCH ts_bspline_default( bspline ); - - ETRY - return err; + size_t i; + for (i = 0; i < dim; i++) + out[i] = x[i] + y[i]; } - -tsError ts_bspline_derive( const tsBSpline* original, - tsBSpline* derivative - ) +void +ts_vec_sub(const tsReal *x, + const tsReal *y, + size_t dim, + tsReal *out) { - tsError err; - jmp_buf buf; - - TRY( buf, err ) - ts_internal_bspline_derive( original, derivative, buf ); - CATCH - - if( original != derivative ) - ts_bspline_default( derivative ); - - ETRY - return err; + size_t i; + if (x == y) { + /* More stable version. */ + ts_arr_fill(out, dim, (tsReal) 0.0); + return; + } + for (i = 0; i < dim; i++) + out[i] = x[i] - y[i]; } - -tsError ts_deboornet_copy( const tsDeBoorNet* original, - tsDeBoorNet* copy - ) +tsReal +ts_vec_dot(const tsReal *x, + const tsReal *y, + size_t dim) { - tsError err; - jmp_buf buf; - - TRY( buf, err ) - ts_internal_deboornet_copy( original, copy, buf ); - CATCH - - if( original != copy ) - ts_deboornet_default( copy ); - - ETRY - return err; + size_t i; + tsReal dot = 0; + for (i = 0; i < dim; i++) + dot += x[i] * y[i]; + return dot; } - -tsError ts_bspline_copy( const tsBSpline* original, - tsBSpline* copy - ) +tsReal +ts_vec_angle(const tsReal *x, + const tsReal *y, + tsReal *buf, + size_t dim) { - tsError err; - jmp_buf buf; - - TRY( buf, err ) - ts_internal_bspline_copy( original, copy, buf ); - CATCH - - if( original != copy ) - ts_bspline_default( copy ); - - ETRY - return err; + const tsReal *x_norm, *y_norm; + if (buf) { + ts_vec_norm(x, dim, buf); + ts_vec_norm(y, dim, buf + dim); + x_norm = buf; + y_norm = buf + dim; + } else { + x_norm = x; + y_norm = y; + } + return (tsReal) ( + /* Use doubles as long as possible. */ + acos(ts_vec_dot(x_norm, + y_norm, + dim)) + * (180.0 / TS_PI) /* radiant to degree */ + ); } - -tsError ts_bspline_set_ctrlp( const tsBSpline* bspline, const tsReal* ctrlp, - tsBSpline* result - ) +void +ts_vec3_cross(const tsReal *x, + const tsReal *y, + tsReal *out) { - tsError err; - jmp_buf buf; - - TRY( buf, err ) - ts_internal_bspline_set_ctrlp( bspline, ctrlp, result, buf ); - CATCH - - if( bspline != result ) - ts_bspline_default( result ); - - ETRY - return err; + tsReal a, b, c; + a = x[1] * y[2] - x[2] * y[1]; + b = x[2] * y[0] - x[0] * y[2]; + c = x[0] * y[1] - x[1] * y[0]; + out[0] = a; + out[1] = b; + out[2] = c; } - -tsError ts_bspline_set_knots( const tsBSpline* bspline, const tsReal* knots, - tsBSpline* result - ) +void +ts_vec_norm(const tsReal *x, + size_t dim, + tsReal *out) { - tsError err; - jmp_buf buf; - - TRY( buf, err ) - ts_internal_bspline_set_knots( bspline, knots, result, buf ); - CATCH - - if( bspline != result ) - ts_bspline_default( result ); - - ETRY - return err; + size_t i; + const tsReal m = ts_vec_mag(x, dim); + if (m < TS_LENGTH_ZERO) { + ts_arr_fill(out, dim, (tsReal) 0.0); + return; + } + for (i = 0; i < dim; i++) + out[i] = x[i] / m; } - -tsError ts_bspline_fill_knots( const tsBSpline* original, - tsBSplineType type, - tsReal min, - tsReal max, - tsBSpline* result - ) +tsReal +ts_vec_mag(const tsReal *x, + size_t dim) { - tsError err; - jmp_buf buf; - - TRY( buf, err ) - ts_internal_bspline_fill_knots( original, type, min, max, result, buf ); - CATCH - - if( original != result ) - ts_bspline_default( result ); - - ETRY - return err; + size_t i; + tsReal sum = 0; + for (i = 0; i < dim; i++) + sum += (x[i] * x[i]); + return (tsReal) sqrt(sum); } - -tsError ts_bspline_evaluate( const tsBSpline* bspline, tsReal u, - tsDeBoorNet* deBoorNet - ) +void +ts_vec_mul(const tsReal *x, + size_t dim, + tsReal val, + tsReal *out) { - tsError err; - jmp_buf buf; + size_t i; + for (i = 0; i < dim; i++) + out[i] = x[i] * val; +} +/*! @} */ - TRY( buf, err ) - ts_internal_bspline_evaluate( bspline, u, deBoorNet, buf ); - CATCH ts_deboornet_default( deBoorNet ); - ETRY - return err; + +/*! @name Chord Length Method + * + * @{ + */ +tsError +ts_chord_lengths_length_to_knot(const tsReal *knots, + const tsReal *lengths, + size_t num, + tsReal len, + tsReal *knot, + tsStatus *status) +{ + tsReal numer, denom, r, r_hat; + size_t idx, low, high; + + /* Handle spacial cases. */ + if (num == 0) { /* well... */ + TS_RETURN_0(status, TS_NO_RESULT, "empty chord lengths") + } + if (num == 1) { /* no computation needed */ + *knot = knots[0]; + TS_RETURN_SUCCESS(status) + } + if (lengths[num - 1] < TS_LENGTH_ZERO) { /* spline is too short */ + *knot = knots[0]; + TS_RETURN_SUCCESS(status) + } + if (len <= lengths[0]) { /* clamp `len' to lower bound */ + *knot = knots[0]; + TS_RETURN_SUCCESS(status) + } + if (len >= lengths[num - 1]) { /* clamp `len' to upper bound */ + *knot = knots[num - 1]; + TS_RETURN_SUCCESS(status) + } + + /* From now on: i) `len' is less than the last chord length in + `lengths' and ii) `lengths' contains at least two values. Hence, the + index (`idx') determined by the following binary search cannot be + the last index in `knots' and `lengths', respectively (i.e., `idx <= + num - 2`). It is therefore safe to access `knots' and `lengths' at + index `idx + 1`. */ + + /* Binary search. Similar to how locating a knot within a knot vector + is implemented in ::ts_int_bspline_find_knot. */ + low = 0; + high = num - 1; + idx = (low+high) / 2; + while (len < lengths[idx] || len >= lengths[(idx) + 1]) { + if (len < lengths[idx]) high = idx; + else low = idx; + idx = (low+high) / 2; + } + + /* Determine `knot' by linear interpolation. */ + denom = lengths[(idx) + 1] - lengths[idx]; + if (denom < TS_LENGTH_ZERO) { /* segment is too short */ + *knot = knots[idx]; + TS_RETURN_SUCCESS(status) + } + numer = len - lengths[idx]; + r = numer / denom; /* denom >= TS_LENGTH_ZERO */ + r_hat = (tsReal) 1.0 - r; + *knot = r * knots[(idx) + 1] + r_hat * knots[idx]; + TS_RETURN_SUCCESS(status) } - -tsError ts_bspline_insert_knot( const tsBSpline* bspline, tsReal u, size_t n, - tsBSpline* result, size_t* k - ) +tsError +ts_chord_lengths_t_to_knot(const tsReal *knots, + const tsReal *lengths, + size_t num, + tsReal t, + tsReal *knot, + tsStatus *status) { - tsDeBoorNet net; - tsError err; - jmp_buf buf; - - TRY( buf, err ) - ts_internal_bspline_evaluate( bspline, u, &net, buf ); - ts_internal_bspline_insert_knot( bspline, &net, n, result, buf ); - *k = net.k + n; - CATCH - - if( bspline != result ) - ts_bspline_default( result ); - - *k = 0; - ETRY ts_deboornet_free(& net ); - - return err; + /* Delegate error handling. If `num' is `0`, + `ts_chord_lengths_length_to_knot' doesn't read `len' at all. */ + tsReal len = num == 0 ? 0 : t * lengths[num - 1]; + return ts_chord_lengths_length_to_knot(knots, + lengths, + num, + len, + knot, + status); } - -tsError ts_bspline_resize( const tsBSpline* bspline, int n, int back, - tsBSpline* resized - ) +tsError +ts_chord_lengths_equidistant_knot_seq(const tsReal *knots, + const tsReal *lengths, + size_t num, + size_t num_knot_seq, + tsReal *knot_seq, + tsStatus *status) { - tsError err; - jmp_buf buf; + tsError err; + size_t i; + tsReal t, knot; + if (num_knot_seq == 0) TS_RETURN_SUCCESS(status) + TS_TRY(try, err, status) + for (i = 0; i < num_knot_seq; i++) { + t = (tsReal) i / (num_knot_seq - 1); + TS_CALL(try, err, ts_chord_lengths_t_to_knot( + knots, lengths, num, t, &knot, status)) + knot_seq[i] = knot; + } + /* Set `knot_seq[0]` after `knot_seq[num_knot_seq - 1]` to + ensure that `knot_seq[0] = min` if `num_knot_seq` is + `1'. Note that `num_knot_seq` and `num` can't be `0'. */ + knot_seq[num_knot_seq - 1] = knots[num - 1]; + knot_seq[0] = knots[0]; + TS_END_TRY_RETURN(err) +} +/*! @} */ - TRY( buf, err ) - ts_internal_bspline_resize( bspline, n, back, resized, buf ); - CATCH - if( bspline != resized ) - ts_bspline_default( resized ); - ETRY - return err; +/*! @name Utility Functions + * + * @{ + */ +int ts_knots_equal(tsReal x, + tsReal y) +{ + return fabs(x-y) < TS_KNOT_EPSILON ? 1 : 0; } - -tsError ts_bspline_split( const tsBSpline* bspline, tsReal u, - tsBSpline* split, size_t* k - ) +void ts_arr_fill(tsReal *arr, + size_t num, + tsReal val) { - tsError err; - jmp_buf buf; - - TRY( buf, err ) - ts_internal_bspline_split( bspline, u, split, k, buf ); - CATCH - - if( bspline != split ) - ts_bspline_default( split ); - - ETRY - return err; + size_t i; + for (i = 0; i < num; i++) + arr[i] = val; } - -tsError ts_bspline_buckle( const tsBSpline* bspline, tsReal b, - tsBSpline* buckled - ) +tsReal ts_distance(const tsReal *x, + const tsReal *y, + size_t dim) { - tsError err; - jmp_buf buf; - - TRY( buf, err ) - ts_internal_bspline_buckle( bspline, b, buckled, buf ); - CATCH - - if( bspline != buckled ) - ts_bspline_default( buckled ); - - ETRY - return err; + size_t i; + tsReal sum = 0; + for (i = 0; i < dim; i++) + sum += (x[i] - y[i]) * (x[i] - y[i]); + return (tsReal) sqrt(sum); } +/*! @} */ - -tsError ts_bspline_to_beziers( const tsBSpline* bspline, - tsBSpline* beziers - ) -{ - tsError err; - jmp_buf buf; - - TRY( buf, err ) - ts_internal_bspline_to_beziers( bspline, beziers, buf ); - CATCH - - if( bspline != beziers ) - ts_bspline_default( beziers ); - - ETRY - return err; -} - - -int ts_fequals( tsReal x, tsReal y ) -{ - if( fabs( x - y ) <= FLT_MAX_ABS_ERROR ) - { - return 1; - } - // KIDAD FIX for divide-by-zero - else if( y == 0.0 ) - { - return x == 0.0; - } - else - { - const tsReal r = (tsReal) fabs( x ) > (tsReal) fabs( y ) ? - (tsReal) fabs( (x - y) / x ) : (tsReal) fabs( (x - y) / y ); - return r <= FLT_MAX_REL_ERROR; - } -} - - -const char* ts_enum_str( tsError err ) -{ - if( err == TS_MALLOC ) - return "malloc failed"; - else if( err == TS_DIM_ZERO ) - return "dim == 0"; - else if( err == TS_DEG_GE_NCTRLP ) - return "deg >= #ctrlp"; - else if( err == TS_U_UNDEFINED ) - return "spline is undefined at given u"; - else if( err == TS_MULTIPLICITY ) - return "s > order"; - else if( err == TS_KNOTS_DECR ) - return "decreasing knot vector"; - else if( err == TS_NUM_KNOTS ) - return "unexpected number of knots"; - else if( err == TS_UNDERIVABLE ) - return "spline is not derivable"; - - return "unknown error"; -} - - -tsError ts_str_enum( const char* str ) -{ - if( !strcmp( str, ts_enum_str( TS_MALLOC ) ) ) - return TS_MALLOC; - else if( !strcmp( str, ts_enum_str( TS_DIM_ZERO ) ) ) - return TS_DIM_ZERO; - else if( !strcmp( str, ts_enum_str( TS_DEG_GE_NCTRLP ) ) ) - return TS_DEG_GE_NCTRLP; - else if( !strcmp( str, ts_enum_str( TS_U_UNDEFINED ) ) ) - return TS_U_UNDEFINED; - else if( !strcmp( str, ts_enum_str( TS_MULTIPLICITY ) ) ) - return TS_MULTIPLICITY; - else if( !strcmp( str, ts_enum_str( TS_KNOTS_DECR ) ) ) - return TS_KNOTS_DECR; - else if( !strcmp( str, ts_enum_str( TS_NUM_KNOTS ) ) ) - return TS_NUM_KNOTS; - else if( !strcmp( str, ts_enum_str( TS_UNDERIVABLE ) ) ) - return TS_UNDERIVABLE; - - return TS_SUCCESS; -} - - -void ts_arr_fill( tsReal* arr, size_t num, tsReal val ) -{ - size_t i; - - for( i = 0; i < num; i++ ) - arr[i] = val; -} - - -tsReal ts_ctrlp_dist2( const tsReal* x, const tsReal* y, size_t dim - ) -{ - tsReal sum = 0; - size_t i; - - for( i = 0; i < dim; i++ ) - sum += (x[i] - y[i]) * (x[i] - y[i]); - - return (tsReal) sqrt( sum ); -} +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/thirdparty/tinyspline_lib/tinyspline.h b/thirdparty/tinyspline_lib/tinyspline.h index 44f458c1a7..b4ea07b8ec 100644 --- a/thirdparty/tinyspline_lib/tinyspline.h +++ b/thirdparty/tinyspline_lib/tinyspline.h @@ -1,912 +1,3115 @@ -/** @file tinyspline.h */ -/* - * The MIT License (MIT) - * - * Copyright (c) 2016 Marcel Steinbeck - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ +/** @file */ + #ifndef TINYSPLINE_H #define TINYSPLINE_H #include -#ifdef __cplusplus -extern "C" { + + +/*! @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 the 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 + * __attribute__ ((visibility ("default"))) 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 as shared library or + * linked against as shared library, \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 + * as static library, \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 -/****************************************************************************** -* * -* System Dependent Configuration * -* * -* The following configuration values must be adjusted to your system. Some of * -* them may be configured using preprocessor definitions. The default values * -* should be fine for most modern hardware, such as x86, x86_64, and arm. * -* * -******************************************************************************/ +#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 - -#define FLT_MAX_ABS_ERROR 1e-5 -#define FLT_MAX_REL_ERROR 1e-8 +/*! @} */ -/****************************************************************************** -* * -* Data Types * -* * -* The following section defines all data types available in TinySpline. * -* * -******************************************************************************/ + +/*! @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_ 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_ + * macros is quite similar to that of the \c TS_RETURN_ macros. Both + * "macro groups" are used to report errors. The difference between \c + * TS_RETURN_ and TS_THROW_, 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_): + * + * 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_). + * + * ::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 + * Return On Error). 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. + * + * @{ + */ /** - * Contains all error codes used by TinySpline. The following code snippet - * shows how to handle errors: - * - * tsError err = ... // any function call here - * if (err < 0) { // or use err != TS_SUCCESS - * printf("we got an error!"); - * - * // you may want to reuse error codes - * return err; - * } + * Defines different error codes. */ typedef enum { - /* No error. */ - TS_SUCCESS = 0, + /** No error. */ + TS_SUCCESS = 0, - /* Unable to allocate memory (using malloc/realloc). */ - TS_MALLOC = -1, + /** Memory cannot be allocated (malloc, realloc etc.). */ + TS_MALLOC = -1, - /* The dimension of the control points are 0. */ - TS_DIM_ZERO = -2, + /** Points have dimensionality 0. */ + TS_DIM_ZERO = -2, - /* Degree of spline (deg) >= number of control points (n_ctrlp). */ - TS_DEG_GE_NCTRLP = -3, + /** degree >= num(control_points). */ + TS_DEG_GE_NCTRLP = -3, - /* Spline is not defined at knot value u. */ - TS_U_UNDEFINED = -4, + /** Knot is not within the domain. */ + TS_U_UNDEFINED = -4, - /* Multiplicity of a knot (s) > order of spline. */ - TS_MULTIPLICITY = -5, + /** multiplicity(knot) > order */ + TS_MULTIPLICITY = -5, - /* Decreasing knot vector. */ - TS_KNOTS_DECR = -6, + /** Decreasing knot vector. */ + TS_KNOTS_DECR = -6, - /* Unexpected number of knots. */ - TS_NUM_KNOTS = -7, + /** Unexpected number of knots. */ + TS_NUM_KNOTS = -7, - /* Spline is not derivable */ - TS_UNDERIVABLE = -8 + /** 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; /** - * Describes how the knot vector of a spline is organized. If you don't know - * what an opened or clamped spline is, have a look at: + * 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 { - /* Not available/Undefined. */ - TS_NONE = 0, + /** Uniformly spaced knot vector with opened end knots. */ + TS_OPENED = 0, - /* Uniformly spaced knot vector. */ - TS_OPENED = 1, + /** Uniformly spaced knot vector with clamped end knots. */ + TS_CLAMPED = 1, - /* Uniformly spaced knot vector with clamped end knots. */ - TS_CLAMPED = 2, - - /* Uniformly spaced knot vector with s(u) = order of spline. */ - TS_BEZIERS = 3 + /** + * Uniformly spaced knot vector where the multiplicity of each knot is + * equal to the order of the spline. + */ + TS_BEZIERS = 2 } tsBSplineType; /** - * Represents a B-Spline which may also be used for NURBS, Bezier curves, - * lines, and points. NURBS are represented using homogeneous coordinates where - * the last component of a control point is its weight. Bezier curves are - * B-Splines with 'n_ctrlp == order' and clamped knot vector making the curve - * passing through the first and last control point. If a Bezier curve consists - * of two control points only, we call them a line. Points, ultimately, are - * just very short lines having only a single control point. Consequently, the - * degree of a point is zero. + * A three-dimensional TNB-vector with position. More details can be found at: * - * Two dimensional control points are organized as follows: + * https://en.wikipedia.org/wiki/Frenet-Serret_formulas + * https://www.math.tamu.edu/~tkiffe/calc3/tnb/tnb.html * - * [x_0, y_0, x_1, y_1, ..., x_n-1, y_n-1] - * - * Tree dimensional control points are organized 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. NURBS are represented using homogeneous coordinates. For - * instance, let's say we have a NURBS in 2D consisting of 11 control points - * where 'w_i' is the weight of the i'th control point. Then the corresponding - * control points are organized as follows: - * - * [x_0, y_0, w_0, x_1, y_1, w_1, ..., x_10, y_10, w_10] - * - * Note: The fields 'ctrlp' and 'knots' share the same array (similar to the - * approach used in 'tsDeBoorNet'). That is, the first elements of this - * array contain the control points of a spline whereas the last elements - * contain its knots. Accordingly, you should never free 'knots' - * explicitly. Using 'ts_bspline_free()' to free dynamically allocated - * memory is to be preferred anyway. If 'ctrlp' and 'knots' do not share - * the same array, or at least a consistent block of data, functions - * provided by TinySpline my fail because values are copied block wise. + * TNB stands for \e tangent, \e normal, and \e binormal. */ typedef struct { - /* Degree of B-Spline basis function. */ - size_t deg; + /** Position of the TNB-vector. */ + tsReal position[3]; - /* A convenience field for deg+1. */ - size_t order; + /** Tangent of the TNB-vector. */ + tsReal tangent[3]; - /* Dimension of a control points. */ - size_t dim; + /** Normal of the TNB-vector. */ + tsReal normal[3]; - /* Number of control points. */ - size_t n_ctrlp; + /** Binormal of the TNB-vector. */ + tsReal binormal[3]; +} tsFrame; +/*! @} */ - /* Number of knots (n_ctrlp + deg + 1). */ - size_t n_knots; - /* Control points of a spline. */ - tsReal* ctrlp; - /* Knot vector of a spline (ascending order). */ - tsReal* knots; +/*! @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 given knot value 'u' by iteratively computing a net of - * intermediate values until the result is available: + * 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 the net are stored in 'points'. The resulting point of an - * evaluation is the last point in 'points' and, for the sake of convenience, - * may be accessed using 'result': + * 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. * - * tsDeBoorNet net = ... // evaluate an arbitrary spline and store - * // resulting net of points in 'net' - * - * net.result ... // use 'result' to access resulting point - * - * Note: You should never free 'result' explicitly as it is just a convenient - * accessor for the last point in 'points'. Using 'ts_deboornet_free()' - * to free dynamically allocated memory is to be preferred anyway. - * - * Two dimensional points are organized as follows: + * 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 organized as follows: + * 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. + * ... and so on. The output also supports homogeneous coordinates + * (cf. ::tsBSpline). * - * There is a special case in which the evaluation of a knot value 'u' returns - * two instead of one result. It occurs when the multiplicity of 'u' ( s(u) ) - * is equals to a spline's order indicating that the spline is discontinuous at - * 'u'. This is common practice for B-Splines (or NURBS) consisting of - * connected Bezier curves where the endpoint of curve 'c_i' is equals to the - * start point of curve 'c_i+1'. The end point of 'c_i' and the start point of - * 'c_i+1' may still be completely different though, yielding to a spline - * having a (real and visible) gap at 'u'. Consequently, De Boor's algorithm - * must return two results if 's(u) == order' in order to give you access to - * the desired points. In such case, 'points' stores only the two resulting - * points (there is no net to create) and 'result' points to the *first* point - * in 'points' ('points' and 'result' store the same pointer). Since having - * (real) gaps in splines is unusual, both points in 'points' are generally - * equals making it easy to handle this special case by accessing 'result' as - * already shown above for regular cases: + * 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: * - * tsDeBoorNet net = ... // evaluate a spline which is discontinuous at - * // at given knot value yielding to a net with - * // two results + * ts_deboornet_result(...)[0] ... // Access the first component of + * // the first result. * - * net.result ... // use 'result' to access resulting point + * ts_deboornet_result(...)[dim(spline)] // Access the first component of + * // the second result. * - * However, you can use both points if necessary: + * 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. * - * tsDeBoorNet net = ... // evaluate a spline which is discontinuous at - * // at given knot value yielding to a net with - * // two results + * In summary, there are three different types of evaluation: * - * net.result[0] ... // 'result[0]' stores the first component of - * // the first point + * 1. The regular case, in which all points of the net are returned. * - * net.result[net.dim] // 'result[net.dim]' stores the first component - * // of the second point + * 2. A special case, in which two results are returned (required for splines + * with gaps). * - * As if this wasn't complicated enough, there is an exception for our special - * case yielding to exactly one result (just like the regular case) even if - * 's(u) == order'. It occurs when 'u' is the lower or upper bound of a - * spline's domain. For instance, if 'b' is a spline with domain [0, 1] and is - * evaluated at 'u = 0' or 'u = 1' then 'result' is *always* a single point - * regardless of the multiplicity of 'u'. Hence, handling this exception is - * straightforward: + * 3. The exception of 2., in which exactly one result is returned (even if \c + * s(u) == \c order). * - * tsDeBoorNet net = ... // evaluate a spline at lower or upper bound of - * // its domain, for instance, 0 or 1 - * - * net.result ... // use 'result' to access resulting point - * - * In summary, we have three different types of evaluation. 1) The regular case - * returning all points of the net we used to calculate the resulting point. 2) - * The special case returning exactly two points which is required for splines - * having (real) gaps. 3) The exception of 2) returning exactly one point even - * if 's(u) == order'. All in all this looks quite complex (and actually it is) - * but for most applications you don't need to bother with them. Just use - * 'result' to access your evaluation point. + * 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 { - /* The evaluated knot value. */ - tsReal u; - - /* The index [u_k, u_k+1) */ - size_t k; - - /* Multiplicity of u_k. */ - size_t s; - - /* How many times u must be inserted to get the resulting point. */ - size_t h; - - /* Dimension of a control point. */ - size_t dim; - - /* Number of points in 'points'. */ - size_t n_points; - - /* Points of the net used to evaluate u_k. */ - tsReal* points; - - /* A convenient pointer to the result in 'points'. */ - tsReal* result; + struct tsDeBoorNetImpl *pImpl; /**< The actual implementation. */ } tsDeBoorNet; - -/****************************************************************************** -* * -* Constructors, Destructors, Copy, and Move Functions * -* * -* The following section contains functions to create and delete instances of * -* the data types listed above. Additionally, each data type has a copy and * -* move function. * -* * -******************************************************************************/ /** - * The default constructor of tsBSpline. + * Returns the knot (sometimes also referred to as \c u or \c t) of \p net. * - * All values of \p \_spline\_ are set to 0/NULL. - * - * @param \_spline\_ - * The spline whose values are set 0/NULL. + * @param[in] net + * The net whose knot is read. + * @return + * The knot of \p net. */ -void ts_bspline_default( tsBSpline* _spline_ ); +tsReal TINYSPLINE_API +ts_deboornet_knot(const tsDeBoorNet *net); /** - * A convenient constructor for tsBSpline. + * Returns the index of the knot of \p net. * - * On error, all values of \p \_spline\_ are set to 0/NULL. + * @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 n_ctrlp - * The number of control points of \p \_spline\_. - * @param dim - * The dimension of each control point in \p \_spline\_. - * @param deg - * The degree of \p \_spline\_. - * @param type - * How to setup the knot vector of \p \_spline\_. - * @param \_spline\_ - * The output parameter storing the result of this function. + * @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_DIM_ZERO - * If \p deg == 0. - * @return TS_DEG_GE_NCTRLP - * If \p deg >= \p n_ctrlp. - * @return TS_NUM_KNOTS - * If \p type == ::TS_BEZIERS and (\p n_ctrlp % \p deg + 1) != 0. + * On success. * @return TS_MALLOC - * If allocating memory failed. + * If allocating memory failed. */ -tsError ts_bspline_new( size_t n_ctrlp, size_t dim, size_t deg, - tsBSplineType type, tsBSpline* _spline_ ); +tsError TINYSPLINE_API +ts_deboornet_points(const tsDeBoorNet *net, + tsReal **points, + tsStatus *status); /** - * The copy constructor of tsBSpline. + * Returns the length of the result array of \p net. * - * Creates a deep copy of \p original and stores the result in \p \_copy\_. + * @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). * - * On error, all values of \p \_copy\_ are set to 0/NULL. Does nothing, if - * \p original == \p \_copy\_. + * @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 original - * The spline to deep copy. - * @param \_copy\_ - * The output parameter storing the copied values of \p original. + * @param[in] net + * The net whose size of the result array is read. * @return TS_SUCCESS - * On success. - * @return TS_MALLOC - * If allocating memory failed. + * The size of the result array of \p net. */ -tsError ts_bspline_copy( const tsBSpline* original, tsBSpline* _copy_ ); +size_t TINYSPLINE_API +ts_deboornet_sof_result(const tsDeBoorNet *net); /** - * The move constructor of tsBSpline. + * 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. * - * Moves all values from \p from to \p \_to\_ and calls ::ts_bspline_default - * on \p from afterwards. Does nothing, if \p from == \p \_to\_. - * - * @param from - * The spline whose values are moved to \p \_to\_. - * @param \_to\_ - * The output parameter storing the moved values of \p from. + * @param[in] net + * The net whose pointer to the result array is returned. + * @return + * Pointer to the result array of \p net. */ -void ts_bspline_move( tsBSpline* from, tsBSpline* _to_ ); +const tsReal TINYSPLINE_API * +ts_deboornet_result_ptr(const tsDeBoorNet *net); /** - * The destructor of tsBSpline. + * Returns a deep copy of the result of \p net. * - * Frees all dynamically allocated memory in \p \_spline\_ and calls - * ::ts_bspline_default afterwards. - * - * @param \_spline\_ - * The spline to free. - */ -void ts_bspline_free( tsBSpline* _spline_ ); - -/** - * The default constructor of tsDeBoorNet. - * - * All values of \p \_deBoorNet\_ are set to 0/NULL. - * - * @param \_deBoorNet\_ - * The net whose values are set 0/NULL. - */ -void ts_deboornet_default( tsDeBoorNet* _deBoorNet_ ); - -/** - * The copy constructor of tsDeBoorNet. - * - * Creates a deep copy of \p original and stores the result in \p \_copy\_. - * - * On error, all values of \p _copy_ are set to 0/NULL. Does nothing, if - * \p original == \p \_copy\_. - * - * @param original - * The net to deep copy. - * @param \_copy\_ - * The output parameter storing the copied values of \p original. + * @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. + * On success. * @return TS_MALLOC - * If allocating memory failed. + * If allocating memory failed. */ -tsError ts_deboornet_copy( const tsDeBoorNet* original, tsDeBoorNet* _copy_ ); +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); /** - * The move constructor of tsDeBoorNet. + * 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. * - * Moves all values from \p from to \p \_to\_ and calls ::ts_deboornet_default - * on \p from afterwards. Does nothing, if \p from == \p \_to\_. + * \b Note: Unlike \e memcpy and \e memmove, the first parameter is the source + * and the second parameter is the destination. * - * @param from - * The net whose values are moved to \p \_to\_. - * @param \_to\_ - * The output parameter storing the moved values of \p from. + * @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. */ -void ts_deboornet_move( tsDeBoorNet* from, tsDeBoorNet* _to_ ); +tsError TINYSPLINE_API +ts_deboornet_copy(const tsDeBoorNet *src, + tsDeBoorNet *dest, + tsStatus *status); /** - * The destructor of tsDeBoorNet. + * 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). * - * Frees all dynamically allocated memory in \p \_deBoorNet\_ and calls - * ::ts_deboornet_default afterwards. - * - * @param \_deBoorNet\_ - * The net to free. + * @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 ts_deboornet_free( tsDeBoorNet* _deBoorNet_ ); +void TINYSPLINE_API +ts_deboornet_move(tsDeBoorNet *src, + tsDeBoorNet *dest); - -/****************************************************************************** -* * -* Interpolation and Approximation Functions * -* * -* The following section contains functions to interpolate and approximate * -* arbitrary splines. * -* * -******************************************************************************/ /** - * Performs a cubic spline interpolation using the thomas algorithm, see: + * 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 resulting spline is a sequence of bezier curves connecting each point - * in \p points. Each bezier curve _b_ is of degree 3 with \p dim being the - * dimension of the each control point in _b_. The total number of control - * points is (\p n - 1) * 4. + * 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: * - * On error, all values of \p \_spline\_ are set to 0/NULL. + * min(1, \p num_points - 1) * 4 * - * Note: \p n is the number of points in \p points and not the length of - * \p points. For instance, the follwing point vector yields to \p n = 4 and - * \p dim = 2: + * 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 points - * The points to interpolate. - * @param n - * The number of points in \p points. - * @param dim - * The dimension of each control point in \p \_spline\_. - * @param \_spline\_ - * The output parameter storing the result of this function. + * @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. + * On success. * @return TS_DIM_ZERO - * If \p dim == 0. - * @return TS_DEG_GE_NCTRLP - * If \p n < 2. + * If \p dimension is 0. + * @return TS_NUM_POINTS + * If \p num_points is 0. * @return TS_MALLOC - * If allocating memory failed. + * If allocating memory failed. */ -tsError ts_bspline_interpolate_cubic( const tsReal* points, size_t n, - size_t dim, tsBSpline* _spline_ ); +tsError TINYSPLINE_API +ts_bspline_interpolate_cubic_natural(const tsReal *points, + size_t num_points, + size_t dimension, + tsBSpline *spline, + tsStatus *status); - -/****************************************************************************** -* * -* Query Functions * -* * -* The following section contains functions to query splines. * -* * -******************************************************************************/ /** - * Evaluates \p spline at knot value \p u and stores the result in - * \p \_deBoorNet\_. + * 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). * - * On error, all values of \p \_deBoorNet\_ are set to 0/NULL. - * - * @param spline - * The spline to evaluate. - * @param u - * The knot value to evaluate. - * @param \_deBoorNet\_ - * The output parameter storing the evaluation result. + * @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_MULTIPLICITY - * If multiplicity s(\p u) > order of \p spline. - * @return TS_U_UNDEFINED - * If \p spline is not defined at knot value \p u. + * 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. + * If allocating memory failed. */ -tsError ts_bspline_evaluate( const tsBSpline* spline, tsReal u, - tsDeBoorNet* _deBoorNet_ ); +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); +/*! @} */ -/****************************************************************************** -* * -* Transformation functions * -* * -* TinySpline is a library focusing on transformations. That is, most * -* functions are used to transform splines by modifying their state, e.g., * -* their number of control points, their degree, and so on. Accordingly, each * -* transformation functions specifies an input and output parameter (along * -* with the other parameters required to calculate the actual transformation). * -* By passing a different pointer to the output parameter, the transformation * -* result is calculated and stored without changing the state of the input. * -* This is in particular useful when dealing with errors as the original state * -* will never be modified. For instance, let's have a look at the following * -* code snippet: * -* * -* tsBSpline in = ... // an arbitrary spline * -* tsBSpline out; // result of transformation * -* * -* // Subdivide 'in' into sequence of bezier curves and store the result * -* // in 'out'. Does not change 'in' in any way. * -* tsError err = ts_bspline_to_beziers(&in, &out); * -* if (err != TS_SUCCESS) { * -* // fortunately, 'in' has not been changed * -* } * -* * -* Even if 'ts_bspline_to_beziers' fails, the state of 'in' has not been * -* changed allowing you to handle the error properly. * -* * -* Unless stated otherwise, the order of the parameters for transformation * -* functions is: * -* * -* function(input, [additional_input], output, [additional_output]) * -* * -* 'additional_input' are parameters required to calculate the actual * -* transformation. 'additional_output' are parameters storing further result. * -* * -* Note: None of TinySpline's transformation functions frees the memory of the * -* output parameter. Thus, when using the same output parameter multiple * -* times, make sure to free memory before each call. Otherwise, you will * -* have a bad time with memory leaks: * -* * -* tsBSpline in = ... // an arbitrary spline * -* tsBSpline out; // result of transformations * -* * -* ts_bspline_to_beziers(&in, &out); // first transformation * -* ... // some code * -* ts_bspline_free(&out); // avoid memory leak. * -* ts_bspline_buckle(&in, &out); // next transformation * -* * -* If you want to modify your input directly without having a separate output, * -* pass it as input and output at once: * -* * -* tsBSpline s = ... // an arbitrary spline * -* tsReal *knots = ... // a knot vector * -* * -* ts_bspline_set_knots(&s, knots, &s); // copy 'knots' into 's' * -* * -* Note: If a transformation function fails *and* input != output, all fields * -* of the output parameter are set to 0/NULL. If input == output, your * -* input may have an invalid state in case of errors. * -* * -******************************************************************************/ + +/*! @name Query Functions + * + * Functions for querying different kinds of data from splines. + * + * @{ + */ /** - * Computes the derivative of \p spline, see: + * 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 \p knot. + * @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); + +/** + * Computes the cumulative chord lengths of the points of the given + * knots. Note that the first length (i.e., lengths[0]) is + * always \c 0, even if the minimum of the domain of \p spline is less + * than the first knot (i.e., knots[0]). Also, the returned + * lengths may be inaccurate if \p spline is discontinuous (i.e., the + * multiplicity of one of the interior knots is equal to the order of + * \p spline) with unequal evaluation points---in such case only the + * first result of the evaluation is included in the calculation. + * + * @pre \p knots and \p lengths have length \p num. + * @param[in] spline + * The spline to query. + * @param[in] knots + * The knots to evaluate \p spline at. + * @param[in] num + * Number of knots in \p knots. + * @param[out] lengths + * The cumulative chord lengths. lengths[i] is the length of \p + * spline at knot i (knots[i]). + * @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 knots in \p knots. + * @return TS_KNOTS_DECR + * If \p knots is not monotonically increasing. + * @return TS_MALLOC + * If memory allocation failed. + */ +tsError TINYSPLINE_API +ts_bspline_chord_lengths(const tsBSpline *spline, + const tsReal *knots, + size_t num, + tsReal *lengths, + tsStatus *status); + +/** + * Extracts a sub-spline from \p spline with respect to the given domain + * [knot0, knot1]. The knots \p knot0 and \p knot1 must lie within the + * domain of \p spline and must not be equal according to + * ::ts_knots_equal. However, \p knot0 can be greater than \p knot1. In this + * case, the control points of the sub-spline are reversed. + + * @param[in] spline + * The spline to query. + * @param[in] knot0 + * Lower bound of the domain of the queried sub-spline if \p knot0 is less + * than \p knot1. Upper bound otherwise. + * @param[in] knot1 + * Upper bound of the domain of the queried sub-spline if \p knot1 is + * greater than \p knot0. Lower bound otherwise. + * @param[out] sub + * The queried sub-spline. + * @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 knot0 or \p knot1. + * @return TS_NO_RESULT + * If \p knot0 and \p knot1 are equal according to ::ts_knots_equal. + * @return TS_MALLOC + * If memory allocation failed. + */ +tsError TINYSPLINE_API +ts_bspline_sub_spline(const tsBSpline *spline, + tsReal knot0, + tsReal knot1, + tsBSpline *sub, + tsStatus *status); + +/** + * Generates a sequence of \p num knots with uniform distribution. \e Uniform + * means that consecutive knots in \p 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); + +/** + * Short-cut function for ::ts_chord_lengths_equidistant_knot_seq. The ordering + * of the parameters (in particular, \p num_samples after \p knots) is aligned + * to ::ts_bspline_uniform_knot_seq so that it is easier for users to replace + * one call with the other. + * + * @param[in] spline + * The spline to query. + * @param[in] num + * Number of knots in \p knots. + * @param[out] knots + * Stores the generated knot sequence. + * @param[in] num_samples + * Number of knots to be sampled for the 'reparametrization by arc length'. + * \c 200 yields a quite precise mapping (subpixel accuracy). For very, + * very high precision requirements, \c 500 should be sufficient. If \c 0, + * the default value \c 200 is used as fallback. + * @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_equidistant_knot_seq(const tsBSpline *spline, + size_t num, + tsReal *knots, + size_t num_samples, + tsStatus *status); +/*! @} */ + + + +/*! @name Transformation Functions + * + * Transformations modify the internal state of a spline---e.g., the number of + * control points, the structure of the knot vector, the degree, and so on. Some + * transformations modify the state without changing the shape of the spline + * (e.g., ::ts_bspline_elevate_degree). 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 object (output parameter for error handling). Along + * with these parameters, additional parameters may be necessary to i) calculate + * the transformation (such as ::ts_bspline_tension) and ii) store additional + * results (such as ::ts_bspline_insert_knot). Unless stated otherwise, the + * order of the parameters of a transformation function, \c t, is: + * + * t(input, [additional_input], output, [additional_output], status) + * + * \b Note: Transformation functions do not releases the memory of the output + * spline before assigning the result of the transformation 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. If the transformation succeeds, the memory of the + * supplied spline is released and the 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 * - * The derivative of a spline _s_ of degree _d_ with _m_ control points and - * _n_ knots is another spline _s'_ of degree _d-1_ with _m-1_ control points - * and _n-2_ knots, defined over _s_ as: + * If \p spline != \p deriv, the internal state of \p spline is not modified, + * that is, \p deriv is a new, independent ::tsBSpline instance. * - * \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 _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 _u_ is _p_ - * rather than _p+1_. - * - * On error, (and if \p spline != \p \_derivative\_) all values of - * \p \_derivative\_ are set to 0/NULL. - * - * @param spline - * The spline to derive. - * @param \_derivative\_ - * The output parameter storing the derivative of \p spline. + * @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. + * On success. * @return TS_UNDERIVABLE - * If \p spline->deg < 1, \p spline->n_ctrlp < 2, or the multiplicity of - * an internal knot of \p spline is greater than the degree of \p spline. - * NOTE: This will be fixed in the future. + * 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. + * If allocating memory failed. */ -tsError ts_bspline_derive( const tsBSpline* spline, tsBSpline* _derivative_ ); +tsError TINYSPLINE_API +ts_bspline_derive(const tsBSpline *spline, + size_t n, + tsReal epsilon, + tsBSpline *deriv, + tsStatus *status); /** - * Creates a deep copy of \p spline (only if \p spline != \p \_result\_) and - * copies the first \p spline->n_ctrlp * \p spline->dim values from \p ctrlp - * to \p \_result\_->ctrlp using memmove. The behaviour of this function is - * undefined, if the length of \p ctrlp is less than \p spline->n_ctrlp * - * \p spline->dim. + * 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. * - * On error, (and if \p spline != \p \_result\_) all values of \p \_result\_ - * are set to 0/NULL. - * - * @param spline - * The spline to deep copy (if \p spline != \p \_result\_) and whose - * control points are replaced with \p ctrlp. - * @param ctrlp - * The control points to copy to \p \_result\_->ctrlp. - * @param \_result\_ - * The output parameter storing the result of this function. + * @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. + * 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 \p spline != \p \_result\_ and allocating memory failed. + * If allocating memory failed. */ -tsError ts_bspline_set_ctrlp( const tsBSpline* spline, const tsReal* ctrlp, - tsBSpline* _result_ ); +tsError TINYSPLINE_API +ts_bspline_insert_knot(const tsBSpline *spline, + tsReal knot, size_t num, + tsBSpline *result, + size_t *k, + tsStatus *status); /** - * Creates a deep copy of \p spline (only if \p spline != \p \_result\_) and - * copies the the first \p spline->n_knots from \p knots to \p \_result\_ - * using memmove. The behaviour of this function is undefined, if the length - * of \p knots is less than \p spline->n_knots. + * 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. * - * On error, (and if \p spline != \p \_result\_) all values of \p \_result\_ - * are set to 0/NULL. - * - * @param spline - * The spline to deep copy (if \p spline != \p \_result\_) and whose - * knots are replaced with \p knots. - * @param knots - * The knots to copy to \p \_result\_->knots. - * @param \_result\_ - * The output parameter storing the result of this function. + * @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. + * On success. + * @return TS_U_UNDEFINED + * If \p spline is not defined at \p knot. * @return TS_MALLOC - * If \p spline != \p \_result\_ and allocating memory failed. + * If allocating memory failed. */ -tsError ts_bspline_set_knots( const tsBSpline* spline, const tsReal* knots, - tsBSpline* _result_ ); +tsError TINYSPLINE_API +ts_bspline_split(const tsBSpline *spline, + tsReal knot, + tsBSpline *split, + size_t *k, + tsStatus *status); /** - * Fills the knot vector of \p spline according to \p type with minimum knot - * value \p min to maximum knot value \p max and stores the result in - * \p \_result\_. Creates a deep copy of \p spline, if - * \p spline != \p \_result\_. - * - * On error, (and if \p spline != \p \_result\_) all values of \p \_result\_ - * are set to 0/NULL. - * - * @param spline - * The spline to deep copy (if \p spline != \p \_result\_) and whose knot - * vector is filled according to \p type with minimum knot value \p min - * and maximum knot value \p max. - * @param type - * How to fill the knot vector of \p \_result\_. - * @param min - * The minimum knot value of the knot vector of \p \_result\_. - * @param max - * The maximum knot value of the knot vector of \p \_result\_. - * @param \_result\_ - * The output parameter storing the result of this function. - * @return TS_SUCCESS - * On success. - * @return TS_DEG_GE_NCTRLP - * If \p spline->n_knots < 2*(\p original->deg+1). We can reuse this - * error code because \p spline->n_knots < 2*(\p spline->deg+1) implies - * \p spline->deg >= \p spline->n_ctrlp. Furthermore, using - * TS_DEG_GE_NCTRLP instead of TS_NUM_KNOTS ensures that TS_NUM_KNOTS is - * not used twice for this function. To be more fail-safe, - * \p spline->deg+1 instead of \p spline->order is used, to make sure - * that \p spline->deg+1 >= 1. - * @return TS_NUM_KNOTS - * If \p type == TS_BEZIERS and - * \p spline->n_knots % \p spline->order != 0. - * @return TS_KNOTS_DECR - * If \p min >= \p max. (::ts_fequals is used to determine whether - * \p min == \p max). - * @return TS_MALLOC - * If \p spline != \p \_result\_ and allocating memory failed. - */ -tsError ts_bspline_fill_knots( const tsBSpline* spline, tsBSplineType type, - tsReal min, tsReal max, tsBSpline* _result_ ); - -/** - * Inserts the knot value \p u \p n times into \p spline and stores the result - * in \p \_result\_. Creates a deep copy of \p spline, if - * \p spline != \p \_result\_. - * - * On error, (and if \p spline != \p \_result\_) all values of \p \_result\_ - * are set to 0/NULL. - * - * @param spline - * The spline to deep copy (if \p spline != \p \_result\_) and whose knot - * vector is extended with \p u \p n times. - * @param u - * The knot value to insert. - * @param n - * How many times \p u should be inserted. - * @param \_result\_ - * The output parameter storing the updated knot vector. - * @param \_k\_ - * The output parameter storing the last index of \p u in \p \_result\_. - * @return TS_SUCCESS - * On success. - * @return TS_MALLOC - * If \p spline != \p \_result\_ and allocating memory failed. - */ -tsError ts_bspline_insert_knot( const tsBSpline* spline, tsReal u, size_t n, - tsBSpline* _result_, size_t* _k_ ); - -/** - * Resizes \p spline by \p n (number of control points) and stores the result - * in \p \_resized\_. Creates a deep copy of \p spline, if - * \p spline != \p \_result\_. If \p back != 0 \p spline is resized at the - * end. If \p back == 0 \p spline is resized at front. - * - * On error, (and if \p spline != \p \_result\_) all values of \p \_result\_ - * are set to 0/NULL. - * - * @return TS_SUCCESS - * On success. - * @return TS_DEG_GE_NCTRLP - * If the degree of \p \_resized\_ would be >= the number of the control - * points of \p \_resized\_. - * @return TS_DIM_ZERO - * If \p spline != \p \_result\_ and \p spline->dim == 0. - * @return TS_DEG_GE_NCTRLP - * If \p spline != \p \_result\_ and - * \p spline->deg >= \p spline->n_ctrlp. - * @return TS_MALLOC - * If \p spline != \p \_result\_ and allocating memory failed. - */ -tsError ts_bspline_resize( const tsBSpline* spline, int n, int back, - tsBSpline* _resized_ ); - -/** - * Splits \p spline at \p u and stores the result in \p \_split\_. That is, - * \p u is inserted _n_ times such that s(\p u) == \p \_split\_->order. - * Creates a deep copy of \p spline, if \p spline != \p \_split\_. - * - * On error, (and if \p spline != \p \_split\_) all values of \p \_split\_ - * are set to 0/NULL. - * - * @param spline - * The spline to deep copy (if \p spline != \p \_result\_) and split. - * @param u - * The split point. - * @param \_split\_ - * The output parameter storing the split spline. - * @param \_k\_ - * The output parameter storing the last index of \p u in \p \_split\_. - * @return TS_SUCCESS - * On success. - * @return TS_MALLOC - * If \p spline != \p \_split\_ and allocating memory failed. - */ -tsError ts_bspline_split( const tsBSpline* spline, tsReal u, - tsBSpline* _split_, size_t* _k_ ); - -/** - * Buckles \p spline by \p b and stores the result in \p \_buckled\_. Creates - * a deep copy of \p spline, if \p spline != \p \_buckled\_. + * 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: * - * Holten, Danny. "Hierarchical edge bundles: Visualization of adjacency - * relations in hierarchical data." Visualization and Computer Graphics, - * IEEE Transactions on 12.5 (2006): 741-748. + * @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). * - * Usually, the range of \p b is: 0.0 <= \p b <= 1.0 with 0 yielding to a line - * connecting the first and the last control point (no buckle) and 1 keeping - * the original shape (maximum buckle). If \b < 0 or \b > 1 the behaviour is - * undefined, though, it will not result in an error. - * - * On error, (and if \p spline != \p \_buckled\_) all values of \p \_buckled\_ - * are set to 0/NULL. - * - * @param spline - * The spline to buckle by \p b. - * @param b - * The buckle factor (usually 0.0 <= \p b <= 1.0). - * @param \_buckled\_ - * The output parameter storing the buckled spline. + * @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. + * On success. * @return TS_MALLOC - * If \p spline != \p \_buckled\_ and allocating memory failed. + * If allocating memory failed. */ -tsError ts_bspline_buckle( const tsBSpline* original, tsReal b, - tsBSpline* _buckled_ ); +tsError TINYSPLINE_API +ts_bspline_tension(const tsBSpline *spline, + tsReal beta, + tsBSpline *out, + tsStatus *status); /** - * Subdivides \p spline into a sequence of Bezier curvs by splitting it at - * each internal knot value. Creates a deep copy of \p spline, if - * \p spline != \p \_beziers\_. + * 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. * - * On error, (and if \p spline != \p \_beziers\_) all values of \p \_beziers\_ - * are set to 0/NULL. - * - * @param spline - * The spline to subdivide into a sequence of Bezier curves. - * @param \_beziers\_ - * The output parameter storing the sequence of Bezier curves. + * @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. + * On success. * @return TS_MALLOC - * If \p spline != \p \_beizers\_ and allocating memory failed. + * If allocating memory failed. */ -tsError ts_bspline_to_beziers( const tsBSpline* spline, tsBSpline* _beziers_ ); +tsError TINYSPLINE_API +ts_bspline_to_beziers(const tsBSpline *spline, + tsBSpline *beziers, + tsStatus *status); - -/****************************************************************************** -* * -* Utility Functions * -* * -* The following section contains utility functions used by TinySpline which * -* also may be helpful when working with this library. * -* * -******************************************************************************/ /** - * Compares the tsReal values \p x and \p y using an absolute and relative - * epsilon environment. + * 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 x - * The x value to compare. - * @param y - * The y value to compare. - * @return 1 - * If \p x is equals to \p y. - * @return 0 - * Otherwise. + * @param[in] spline + * The spline to be elevated. + * @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 discontinuity points that should be retained. + * A viable default value is ::TS_POINT_EPSILON. If negative, the resulting + * spline, \p elevated, forms a sequence of bezier curves. + * @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. */ -int ts_fequals( tsReal x, tsReal y ); +tsError TINYSPLINE_API +ts_bspline_elevate_degree(const tsBSpline *spline, + size_t amount, + tsReal epsilon, + tsBSpline *elevated, + tsStatus *status); /** - * Returns the error message associated to \p err. Returns "unknown error" if - * \p err is no associated (indicating a bug) or is TS_SUCCESS (which is not - * an actual error). - */ -const char* ts_enum_str( tsError err ); - -/** - * Returns the error code associated to \p str or TS_SUCCESS if \p str is not - * associated. Keep in mind that by concept "unknown error" is not associated, - * though, TS_SUCCESS is returned. - */ -tsError ts_str_enum( const char* str ); - -/** - * Fills the given array \p arr with \p val from \p arr+0 to \p arr+ \p num - * (exclusive). - */ -void ts_arr_fill( tsReal* arr, size_t num, tsReal val ); - -/** - * Returns the euclidean distance of \p x and \p y consisting of \p dim - * components, respectively. + * 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 x - * The x value. - * @param y - * The y value. - * @param dim - * The dimension of \p x and \p y. + * @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 euclidean distanc of \p x and \p y. + * The dot product of \p x and \y. */ -tsReal ts_ctrlp_dist2( const tsReal* x, const tsReal* y, size_t dim ); +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 2 * dim. + * @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 0.0 <= angle <= 180.0. + */ +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); +/*! @} */ -#ifdef __cplusplus + +/*! @name Chord Length Method + * + * Functions for processing the cumulative chord lengths of a spline + * as computed by ::ts_bspline_chord_lengths. + * + * @{ + */ +/** + * Maps \p len to a knot, \c k, such that ts_bspline_eval(..., k ...) + * yields a point whose length, with respect to knots[0], is close to + * \p len. Note that \p len is clamped to the domain of \p lengths. The domain + * of the result, \p knot, is [knots[0], knots[num-1]]. + * + * The precision of the mapping depends on the resolution of \p knots and \p + * lengths. That is, the more chord lengths were computed, the more precise the + * length-to-knot-mapping becomes. Generally, \c 200 chord lengths yields a + * quite precise mapping (subpixel accuracy). For very, very high precision + * requirements, \c 500 chord lengths should be sufficient. + * + * Returns ::TS_NO_RESULT if \p num is \c 0. + * + * @pre + * \p lengths is monotonically increasing and contains only non-negative + * values (distances cannot be negative). + * @param[in] knots + * Knots that were passed to ::ts_bspline_chord_lengths. + * @param[in] lengths + * Cumulative chord lengths as computed by ::ts_bspline_chord_lengths. + * @param[in] num + * Number of values in \p knots and \p lengths. + * @param[in] len + * Length to be mapped. Clamped to the domain of \p lengths. + * @param[out] knot + * A knot, such that ts_bspline_eval(..., knot ...) yields a + * point whose length, with respect to knots[0], is close to \p + * len. + * @param[out] status + * The status of this function. May be NULL. + * @return TS_SUCCESS + * On success. + * @return TS_NO_RESULT + * If \p num is \c 0. + */ +tsError +ts_chord_lengths_length_to_knot(const tsReal *knots, + const tsReal *lengths, + size_t num, + tsReal len, + tsReal *knot, + tsStatus *status); + +/** + * Same as ::ts_chord_lengths_length_to_knot, except that this function takes a + * chord length parameter, \p t, with domain [0, 1] (clamped), which indicates + * the to be evaluated relative proportion of the total length + * (lengths[num-1]). + * + * @pre + * \p lengths is monotonically increasing and contains only non-negative + * values (distances cannot be negative). + * @param[in] knots + * Knots that were passed to ::ts_bspline_chord_lengths. + * @param[in] lengths + * Cumulative chord lengths as computed by ::ts_bspline_chord_lengths. + * @param[in] num + * Number of values in \p knots and \p lengths. + * @param[in] t + * Chord length parameter (relative proportion of the total length). + * Clamped to the domain [0, 1]. + * @param[out] knot + * A knot, such that ts_bspline_eval(..., knot ...) yields a + * point whose length, with respect to knots[0], is close to + * t * lengths[num-1] + * @param[out] status + * The status of this function. May be NULL. + * @return TS_SUCCESS + * On success. + * @return TS_NO_RESULT + * If \p num is \c 0. + */ +tsError TINYSPLINE_API +ts_chord_lengths_t_to_knot(const tsReal *knots, + const tsReal *lengths, + size_t num, + tsReal t, + tsReal *knot, + tsStatus *status); + +/** + * Generates a sequence of \p num_knot_seq knots with equidistant distribution. + * \e Equidistant means that the points evaluated from consecutive knots in \p + * knot_seq have the same distance along the spline. This is also known as + * 'reparametrization by arc length'. + * + * @pre + * \p lengths is monotonically increasing and contains only non-negative + * values (distances cannot be negative). + * @param[in] knots + * Knots that were passed to ::ts_bspline_chord_lengths. + * @param[in] lengths + * Cumulative chord lengths as computed by ::ts_bspline_chord_lengths. + * @param[in] num + * Number of values in \p knots and \p lengths. + * @param[in] num_knot_seq + * Number of knots in \p knot_seq. + * @param[out] knot_seq + * Stores the generated knot sequence. + * @param[out] status + * The status of this function. May be NULL. + * @return TS_SUCCESS + * On success. + * @return TS_NO_RESULT + * If \p num is \c 0. + */ +tsError TINYSPLINE_API +ts_chord_lengths_equidistant_knot_seq(const tsReal *knots, + const tsReal *lengths, + size_t num, + size_t num_knot_seq, + tsReal *knot_seq, + tsStatus *status); +/*! @} */ + + + +/*! @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 less 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 */ +#endif /* TINYSPLINE_H */ + diff --git a/thirdparty/tinyspline_lib/tinysplinecpp.cpp b/thirdparty/tinyspline_lib/tinysplinecpp.cpp deleted file mode 100644 index b64c5eb2bd..0000000000 --- a/thirdparty/tinyspline_lib/tinysplinecpp.cpp +++ /dev/null @@ -1,493 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2016 Marcel Steinbeck - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "tinysplinecpp.h" -#include - -/******************************************************** -* * -* DeBoorNet * -* * -********************************************************/ -tinyspline::DeBoorNet::DeBoorNet() -{ - ts_deboornet_default( &deBoorNet ); -} - - -tinyspline::DeBoorNet::DeBoorNet( const tinyspline::DeBoorNet& other ) -{ - const tsError err = ts_deboornet_copy( &other.deBoorNet, &deBoorNet ); - - if( err < 0 ) - throw std::runtime_error( ts_enum_str( err ) ); -} - - -tinyspline::DeBoorNet::~DeBoorNet() -{ - ts_deboornet_free( &deBoorNet ); -} - - -tinyspline::DeBoorNet& tinyspline::DeBoorNet::operator=( const tinyspline::DeBoorNet& other ) -{ - if( &other != this ) - { - const tsError err = ts_deboornet_copy( - &other.deBoorNet, &deBoorNet ); - - if( err < 0 ) - throw std::runtime_error( ts_enum_str( err ) ); - } - - return *this; -} - - -tinyspline::real tinyspline::DeBoorNet::u() const -{ - return deBoorNet.u; -} - - -size_t tinyspline::DeBoorNet::k() const -{ - return deBoorNet.k; -} - - -size_t tinyspline::DeBoorNet::s() const -{ - return deBoorNet.s; -} - - -size_t tinyspline::DeBoorNet::h() const -{ - return deBoorNet.h; -} - - -size_t tinyspline::DeBoorNet::dim() const -{ - return deBoorNet.dim; -} - - -size_t tinyspline::DeBoorNet::nPoints() const -{ - return deBoorNet.n_points; -} - - -std::vector tinyspline::DeBoorNet::points() const -{ - const tinyspline::real* begin = deBoorNet.points; - const tinyspline::real* end = begin + deBoorNet.n_points * deBoorNet.dim; - - return std::vector( begin, end ); -} - - -std::vector tinyspline::DeBoorNet::result() const -{ - const tinyspline::real* begin = deBoorNet.result; - const tinyspline::real* end = begin + deBoorNet.dim; - - return std::vector( begin, end ); -} - - -tsDeBoorNet* tinyspline::DeBoorNet::data() -{ - return &deBoorNet; -} - - -#ifndef TINYSPLINE_DISABLE_CXX11_FEATURES -tinyspline::DeBoorNet::DeBoorNet( tinyspline::DeBoorNet&& other ) noexcept -{ - ts_deboornet_default( &deBoorNet ); - swap( other ); -} - - -tinyspline::DeBoorNet& tinyspline::DeBoorNet::operator=( tinyspline::DeBoorNet&& other ) noexcept -{ - if( &other != this ) - { - ts_deboornet_free( &deBoorNet ); - swap( other ); - } - - return *this; -} - - -void tinyspline::DeBoorNet::swap( tinyspline::DeBoorNet& other ) -{ - if( &other != this ) - { - std::swap( deBoorNet.u, other.deBoorNet.u ); - std::swap( deBoorNet.k, other.deBoorNet.k ); - std::swap( deBoorNet.s, other.deBoorNet.s ); - std::swap( deBoorNet.h, other.deBoorNet.h ); - std::swap( deBoorNet.dim, other.deBoorNet.dim ); - std::swap( deBoorNet.n_points, other.deBoorNet.n_points ); - std::swap( deBoorNet.points, other.deBoorNet.points ); - std::swap( deBoorNet.result, other.deBoorNet.result ); - } -} - - -#endif - - -/******************************************************** -* * -* BSpline * -* * -********************************************************/ -tinyspline::BSpline::BSpline() -{ - ts_bspline_default( &bspline ); -} - - -tinyspline::BSpline::BSpline( const tinyspline::BSpline& other ) -{ - const tsError err = ts_bspline_copy( &other.bspline, &bspline ); - - if( err < 0 ) - throw std::runtime_error( ts_enum_str( err ) ); -} - - -tinyspline::BSpline::BSpline( const size_t nCtrlp, const size_t dim, - const size_t deg, const tinyspline::BSpline::type type ) -{ - const tsError err = ts_bspline_new( nCtrlp, dim, deg, type, &bspline ); - - if( err < 0 ) - throw std::runtime_error( ts_enum_str( err ) ); -} - - -tinyspline::BSpline::~BSpline() -{ - ts_bspline_free( &bspline ); -} - - -tinyspline::BSpline& tinyspline::BSpline::operator=( const tinyspline::BSpline& other ) -{ - if( &other != this ) - { - const tsError err = ts_bspline_copy( &other.bspline, &bspline ); - - if( err < 0 ) - throw std::runtime_error( ts_enum_str( err ) ); - } - - return *this; -} - - -tinyspline::DeBoorNet tinyspline::BSpline::operator()( const tinyspline::real u ) const -{ - return evaluate( u ); -} - - -size_t tinyspline::BSpline::deg() const -{ - return bspline.deg; -} - - -size_t tinyspline::BSpline::order() const -{ - return bspline.order; -} - - -size_t tinyspline::BSpline::dim() const -{ - return bspline.dim; -} - - -size_t tinyspline::BSpline::nCtrlp() const -{ - return bspline.n_ctrlp; -} - - -size_t tinyspline::BSpline::nKnots() const -{ - return bspline.n_knots; -} - - -std::vector tinyspline::BSpline::ctrlp() const -{ - const tinyspline::real* begin = bspline.ctrlp; - const tinyspline::real* end = begin + bspline.n_ctrlp * bspline.dim; - - return std::vector( begin, end ); -} - - -std::vector tinyspline::BSpline::knots() const -{ - const tinyspline::real* begin = bspline.knots; - const tinyspline::real* end = begin + bspline.n_knots; - - return std::vector( begin, end ); -} - - -tsBSpline* tinyspline::BSpline::data() -{ - return &bspline; -} - - -tinyspline::DeBoorNet tinyspline::BSpline::evaluate( const tinyspline::real u ) const -{ - tinyspline::DeBoorNet deBoorNet; - const tsError err = ts_bspline_evaluate( &bspline, u, deBoorNet.data() ); - - if( err < 0 ) - throw std::runtime_error( ts_enum_str( err ) ); - - return deBoorNet; -} - - -void tinyspline::BSpline::setCtrlp( const std::vector& ctrlp ) -{ - if( ctrlp.size() != nCtrlp() * dim() ) - { - throw std::runtime_error( "The number of values must be equals" - "to the spline's number of control points multiplied" - "by the dimension of each control point." ); - } - - const tsError err = ts_bspline_set_ctrlp( - &bspline, ctrlp.data(), &bspline ); - - if( err < 0 ) - throw std::runtime_error( ts_enum_str( err ) ); -} - - -void tinyspline::BSpline::setKnots( const std::vector& knots ) -{ - if( knots.size() != nKnots() ) - { - throw std::runtime_error( "The number of values must be equals" - "to the spline's number of knots." ); - } - - const tsError err = ts_bspline_set_knots( - &bspline, knots.data(), &bspline ); - - if( err < 0 ) - throw std::runtime_error( ts_enum_str( err ) ); -} - - -tinyspline::BSpline tinyspline::BSpline::fillKnots( const tsBSplineType type, - const tinyspline::real min, - const tinyspline::real max ) const -{ - tinyspline::BSpline bs; - const tsError err = ts_bspline_fill_knots( - &bspline, type, min, max, &bs.bspline ); - - if( err < 0 ) - throw std::runtime_error( ts_enum_str( err ) ); - - return bs; -} - - -tinyspline::BSpline tinyspline::BSpline::insertKnot( const tinyspline::real u, - const size_t n ) const -{ - tinyspline::BSpline bs; - size_t k; - const tsError err = ts_bspline_insert_knot( - &bspline, u, n, &bs.bspline, &k ); - - if( err < 0 ) - throw std::runtime_error( ts_enum_str( err ) ); - - return bs; -} - - -tinyspline::BSpline tinyspline::BSpline::resize( const int n, const int back ) const -{ - tinyspline::BSpline bs; - const tsError err = ts_bspline_resize( &bspline, n, back, &bs.bspline ); - - if( err < 0 ) - throw std::runtime_error( ts_enum_str( err ) ); - - return bs; -} - - -tinyspline::BSpline tinyspline::BSpline::split( const tinyspline::real u ) const -{ - tinyspline::BSpline bs; - size_t k; - const tsError err = ts_bspline_split( &bspline, u, &bs.bspline, &k ); - - if( err < 0 ) - throw std::runtime_error( ts_enum_str( err ) ); - - return bs; -} - - -tinyspline::BSpline tinyspline::BSpline::buckle( const tinyspline::real b ) const -{ - tinyspline::BSpline bs; - const tsError err = ts_bspline_buckle( &bspline, b, &bs.bspline ); - - if( err < 0 ) - throw std::runtime_error( ts_enum_str( err ) ); - - return bs; -} - - -tinyspline::BSpline tinyspline::BSpline::toBeziers() const -{ - tinyspline::BSpline bs; - const tsError err = ts_bspline_to_beziers( &bspline, &bs.bspline ); - - if( err < 0 ) - throw std::runtime_error( ts_enum_str( err ) ); - - return bs; -} - - -tinyspline::BSpline tinyspline::BSpline::derive() const -{ - tinyspline::BSpline bs; - const tsError err = ts_bspline_derive( &bspline, &bs.bspline ); - - if( err < 0 ) - throw std::runtime_error( ts_enum_str( err ) ); - - return bs; -} - - -#ifndef TINYSPLINE_DISABLE_CXX11_FEATURES -tinyspline::BSpline::BSpline( tinyspline::BSpline&& other ) noexcept -{ - ts_bspline_default( &bspline ); - swap( other ); -} - - -tinyspline::BSpline& tinyspline::BSpline::operator=( tinyspline::BSpline&& other ) noexcept -{ - if( &other != this ) - { - ts_bspline_free( &bspline ); - swap( other ); - } - - return *this; -} - - -void tinyspline::BSpline::swap( tinyspline::BSpline& other ) -{ - if( &other != this ) - { - std::swap( bspline.deg, other.bspline.deg ); - std::swap( bspline.order, other.bspline.order ); - std::swap( bspline.dim, other.bspline.dim ); - std::swap( bspline.n_ctrlp, other.bspline.n_ctrlp ); - std::swap( bspline.n_knots, other.bspline.n_knots ); - std::swap( bspline.ctrlp, other.bspline.ctrlp ); - std::swap( bspline.knots, other.bspline.knots ); - } -} - - -#endif - - -/******************************************************** -* * -* Utils * -* * -********************************************************/ -tinyspline::BSpline tinyspline::Utils::interpolateCubic( - const std::vector* points, - const size_t dim ) -{ - if( dim == 0 ) - throw std::runtime_error( ts_enum_str( TS_DIM_ZERO ) ); - - if( points->size() % dim != 0 ) - throw std::runtime_error( "#points % dim == 0 failed" ); - - tinyspline::BSpline bspline; - const tsError err = ts_bspline_interpolate_cubic( - points->data(), points->size() / dim, dim, bspline.data() ); - - if( err < 0 ) - throw std::runtime_error( ts_enum_str( err ) ); - - return bspline; -} - - -bool tinyspline::Utils::fequals( const tinyspline::real x, const tinyspline::real y ) -{ - return ts_fequals( x, y ) == 1; -} - - -std::string tinyspline::Utils::enum_str( const tsError err ) -{ - return std::string( ts_enum_str( err ) ); -} - - -tsError tinyspline::Utils::str_enum( const std::string str ) -{ - return ts_str_enum( str.c_str() ); -} diff --git a/thirdparty/tinyspline_lib/tinysplinecpp.h b/thirdparty/tinyspline_lib/tinysplinecpp.h deleted file mode 100644 index 545de4d0e5..0000000000 --- a/thirdparty/tinyspline_lib/tinysplinecpp.h +++ /dev/null @@ -1,143 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2016 Marcel Steinbeck - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "tinyspline.h" -#include -#include - -namespace tinyspline { -typedef tsReal real; - -class DeBoorNet -{ -public: - /* Constructors & Destructors */ - DeBoorNet(); - DeBoorNet( const DeBoorNet& other ); - ~DeBoorNet(); - - /* Operators */ - DeBoorNet& operator=( const DeBoorNet& other ); - - /* Getter */ - real u() const; - size_t k() const; - size_t s() const; - size_t h() const; - size_t dim() const; - size_t nPoints() const; - - std::vector points() const; - - std::vector result() const; - tsDeBoorNet* data(); - - /* C++11 features */ -#ifndef TINYSPLINE_DISABLE_CXX11_FEATURES - DeBoorNet( DeBoorNet&& other ) noexcept; - DeBoorNet& operator=( DeBoorNet&& other ) noexcept; - void swap( DeBoorNet& other ); - - friend void swap( DeBoorNet& left, DeBoorNet& right ) - { - left.swap( right ); - } - -#endif - -private: - tsDeBoorNet deBoorNet; -}; - -class BSpline -{ -public: - typedef tsBSplineType type; - - /* Constructors & Destructors */ - BSpline(); - BSpline( const BSpline& other ); - explicit BSpline( size_t nCtrlp, size_t dim = 2, size_t deg = 3, - tinyspline::BSpline::type type = TS_CLAMPED ); - ~BSpline(); - - /* Operators */ - BSpline& operator=( const BSpline& other ); - DeBoorNet operator()( real u ) const; - - /* Getter */ - size_t deg() const; - size_t order() const; - size_t dim() const; - size_t nCtrlp() const; - size_t nKnots() const; - - std::vector ctrlp() const; - - std::vector knots() const; - tsBSpline* data(); - DeBoorNet evaluate( real u ) const; - - /* Modifications */ - void setCtrlp( const std::vector& ctrlp ); - void setKnots( const std::vector& knots ); - - /* Transformations */ - BSpline fillKnots( tsBSplineType type, real min, real max ) const; - BSpline insertKnot( real u, size_t n ) const; - BSpline resize( int n, int back ) const; - BSpline split( real u ) const; - BSpline buckle( real b ) const; - BSpline toBeziers() const; - BSpline derive() const; - - /* C++11 features */ -#ifndef TINYSPLINE_DISABLE_CXX11_FEATURES - BSpline( BSpline&& other ) noexcept; - BSpline& operator=( BSpline&& other ) noexcept; - void swap( BSpline& other ); - - friend void swap( BSpline& left, BSpline& right ) - { - left.swap( right ); - } - -#endif - -private: - tsBSpline bspline; -}; - -class Utils -{ -public: - static BSpline interpolateCubic( const std::vector* points, size_t dim ); - static bool fequals( real x, real y ); - static std::string enum_str( tsError err ); - static tsError str_enum( std::string str ); - -private: - Utils() {} -}; -} diff --git a/thirdparty/tinyspline_lib/tinysplinecxx.cxx b/thirdparty/tinyspline_lib/tinysplinecxx.cxx new file mode 100644 index 0000000000..80cb93b50b --- /dev/null +++ b/thirdparty/tinyspline_lib/tinysplinecxx.cxx @@ -0,0 +1,1707 @@ +#define TINYSPLINE_EXPORT +#include "tinysplinecxx.h" + +#include +#include +#include +#include +#include + +/* Suppress some useless MSVC warnings. */ +#ifdef _MSC_VER +#pragma warning(push) +/* address of dllimport */ +#pragma warning(disable:4232) +/* binding rvalues to non-const references */ +#pragma warning(disable:4350) +/* unreferenced inline function */ +#pragma warning(disable:4514) +/* function not inlined */ +#pragma warning(disable:4710) +/* byte padding */ +#pragma warning(disable:4820) +/* meaningless deprecation */ +#pragma warning(disable:4996) +/* Spectre mitigation */ +#pragma warning(disable:5045) +#endif + + + +/*! @name Swig Type Mapping + * + * See tinysplinecxx.h for more details. + * + * @{ + */ +#ifdef SWIG +#define std_real_vector_init(var) \ + std_real_vector_out var = new std::vector +#define std_real_vector_read(var) var-> +#else +#define std_real_vector_init(var) \ + std_real_vector_out var +#define std_real_vector_read(var) var. +#endif +/*! @} */ + + + +/*! @name Vec2 + * + * @{ + */ +tinyspline::Vec2::Vec2() +{ + const real v = (real) 0.0; + ts_vec2_init(m_vals, v, v); +} + +tinyspline::Vec2::Vec2(real x, + real y) +{ + ts_vec2_init(m_vals, x, y); +} + +tinyspline::Vec2 +tinyspline::Vec2::operator+(const Vec2 &other) +{ + return add(other); +} + +tinyspline::Vec2 +tinyspline::Vec2::operator-(const Vec2 &other) +{ + return subtract(other); +} + +tinyspline::Vec2 +tinyspline::Vec2::operator*(real scalar) +{ + return multiply(scalar); +} + +tinyspline::real +tinyspline::Vec2::x() const +{ + return m_vals[0]; +} + +void +tinyspline::Vec2::setX(real val) +{ + m_vals[0] = val; +} + +tinyspline::real +tinyspline::Vec2::y() const +{ + return m_vals[1]; +} + +void +tinyspline::Vec2::setY(real val) +{ + m_vals[1] = val; +} + +std::vector +tinyspline::Vec2::values() const +{ + return std::vector({ x(), y() }); +} + +tinyspline::Vec2 +tinyspline::Vec2::add(const Vec2 &other) const +{ + Vec2 vec; + ts_vec_add(m_vals, other.m_vals, 2, vec.m_vals); + return vec; +} + +tinyspline::Vec2 +tinyspline::Vec2::subtract(const Vec2 &other) const +{ + Vec2 vec; + ts_vec_sub(m_vals, other.m_vals, 2, vec.m_vals); + return vec; +} + +tinyspline::Vec2 +tinyspline::Vec2::multiply(real scalar) const +{ + Vec2 vec; + ts_vec_mul(m_vals, 2, scalar, vec.m_vals); + return vec; +} + +tinyspline::Vec2 +tinyspline::Vec2::normalize() const +{ + Vec2 vec; + ts_vec_norm(m_vals, 2, vec.m_vals); + return vec; +} + +tinyspline::Vec2 +tinyspline::Vec2::norm() const +{ + return normalize(); +} + +tinyspline::real +tinyspline::Vec2::magnitude() const +{ + return ts_vec_mag(m_vals, 2); +} + +tinyspline::real +tinyspline::Vec2::dot(const Vec2 &other) const +{ + return ts_vec_dot(m_vals, other.m_vals, 2); +} + +tinyspline::real +tinyspline::Vec2::angle(const Vec2 &other) const +{ + real buf[4]; + return ts_vec_angle(m_vals, other.m_vals, buf, 2); +} + +tinyspline::real +tinyspline::Vec2::distance(const Vec2 &other) const +{ + return ts_distance(m_vals, other.m_vals, 2); +} + +std::string +tinyspline::Vec2::toString() const +{ + std::ostringstream oss; + oss << "Vec2{" + << "x: " << x() + << ", y: " << y() + << "}"; + return oss.str(); +} +/*! @} */ + + + +/*! @name Vec3 + * + * @{ + */ +tinyspline::Vec3::Vec3() +{ + const real v = (real) 0.0; + ts_vec3_init(m_vals, v, v, v); +} + +tinyspline::Vec3::Vec3(real x, + real y, + real z) +{ + ts_vec3_init(m_vals, x, y, z); +} + +tinyspline::Vec3 +tinyspline::Vec3::operator+(const Vec3 &other) +{ + return add(other); +} + +tinyspline::Vec3 +tinyspline::Vec3::operator-(const Vec3 &other) +{ + return subtract(other); +} + +tinyspline::Vec3 +tinyspline::Vec3::operator*(real scalar) +{ + return multiply(scalar); +} + +tinyspline::real +tinyspline::Vec3::x() const +{ + return m_vals[0]; +} + +void +tinyspline::Vec3::setX(real val) +{ + m_vals[0] = val; +} + +tinyspline::real +tinyspline::Vec3::y() const +{ + return m_vals[1]; +} + +void +tinyspline::Vec3::setY(real val) +{ + m_vals[1] = val; +} + +tinyspline::real +tinyspline::Vec3::z() const +{ + return m_vals[2]; +} + +void +tinyspline::Vec3::setZ(real val) +{ + m_vals[2] = val; +} + +std::vector +tinyspline::Vec3::values() const +{ + return std::vector({ x(), y(), z() }); +} + +tinyspline::Vec3 +tinyspline::Vec3::add(const Vec3 &other) const +{ + Vec3 vec; + ts_vec_add(m_vals, other.m_vals, 3, vec.m_vals); + return vec; +} + +tinyspline::Vec3 +tinyspline::Vec3::subtract(const Vec3 &other) const +{ + Vec3 vec; + ts_vec_sub(m_vals, other.m_vals, 3, vec.m_vals); + return vec; +} + +tinyspline::Vec3 +tinyspline::Vec3::multiply(real scalar) const +{ + Vec3 vec; + ts_vec_mul(m_vals, 3, scalar, vec.m_vals); + return vec; +} + +tinyspline::Vec3 +tinyspline::Vec3::cross(const Vec3 &other) const +{ + Vec3 vec; + ts_vec3_cross(m_vals, other.m_vals, vec.m_vals); + return vec; +} + +tinyspline::Vec3 +tinyspline::Vec3::normalize() const +{ + Vec3 vec; + ts_vec_norm(m_vals, 3, vec.m_vals); + return vec; +} + +tinyspline::Vec3 +tinyspline::Vec3::norm() const +{ + return normalize(); +} + +tinyspline::real +tinyspline::Vec3::magnitude() const +{ + return ts_vec_mag(m_vals, 3); +} + +tinyspline::real +tinyspline::Vec3::dot(const Vec3 &other) const +{ + return ts_vec_dot(m_vals, other.m_vals, 3); +} + +tinyspline::real +tinyspline::Vec3::angle(const Vec3 &other) const +{ + real buf[6]; + return ts_vec_angle(m_vals, other.m_vals, buf, 3); +} + +tinyspline::real +tinyspline::Vec3::distance(const Vec3 &other) const +{ + return ts_distance(m_vals, other.m_vals, 3); +} + +std::string +tinyspline::Vec3::toString() const +{ + std::ostringstream oss; + oss << "Vec3{" + << "x: " << x() + << ", y: " << y() + << ", z: " << z() + << "}"; + return oss.str(); +} +/*! @} */ + + + +/*! @name Vec4 + * + * @{ + */ +tinyspline::Vec4::Vec4() +{ + const real v = (real) 0.0; + ts_vec4_init(m_vals, v, v, v, v); +} + +tinyspline::Vec4::Vec4(real x, + real y, + real z, + real w) +{ + ts_vec4_init(m_vals, x, y, z, w); +} + +tinyspline::Vec4 +tinyspline::Vec4::operator+(const Vec4 &other) +{ + return add(other); +} + +tinyspline::Vec4 +tinyspline::Vec4::operator-(const Vec4 &other) +{ + return subtract(other); +} + +tinyspline::Vec4 +tinyspline::Vec4::operator*(real scalar) +{ + return multiply(scalar); +} + +tinyspline::real +tinyspline::Vec4::x() const +{ + return m_vals[0]; +} + +void +tinyspline::Vec4::setX(real val) +{ + m_vals[0] = val; +} + +tinyspline::real +tinyspline::Vec4::y() const +{ + return m_vals[1]; +} + +void +tinyspline::Vec4::setY(real val) +{ + m_vals[1] = val; +} + +tinyspline::real +tinyspline::Vec4::z() const +{ + return m_vals[2]; +} + +void +tinyspline::Vec4::setZ(real val) +{ + m_vals[2] = val; +} + +tinyspline::real +tinyspline::Vec4::w() const +{ + return m_vals[3]; +} + +void +tinyspline::Vec4::setW(real val) +{ + m_vals[3] = val; +} + +std::vector +tinyspline::Vec4::values() const +{ + return std::vector({ x(), y(), z(), w() }); +} + +tinyspline::Vec4 +tinyspline::Vec4::add(const Vec4 &other) const +{ + Vec4 vec; + ts_vec_add(m_vals, other.m_vals, 4, vec.m_vals); + return vec; +} + +tinyspline::Vec4 +tinyspline::Vec4::subtract(const Vec4 &other) const +{ + Vec4 vec; + ts_vec_sub(m_vals, other.m_vals, 4, vec.m_vals); + return vec; +} + +tinyspline::Vec4 +tinyspline::Vec4::multiply(real scalar) const +{ + Vec4 vec; + ts_vec_mul(m_vals, 4, scalar, vec.m_vals); + return vec; +} + +tinyspline::Vec4 +tinyspline::Vec4::normalize() const +{ + Vec4 vec; + ts_vec_norm(m_vals, 4, vec.m_vals); + return vec; +} + +tinyspline::Vec4 +tinyspline::Vec4::norm() const +{ + return normalize(); +} + +tinyspline::real +tinyspline::Vec4::magnitude() const +{ + return ts_vec_mag(m_vals, 4); +} + +tinyspline::real +tinyspline::Vec4::dot(const Vec4 &other) const +{ + return ts_vec_dot(m_vals, other.m_vals, 4); +} + +tinyspline::real +tinyspline::Vec4::angle(const Vec4 &other) const +{ + real buf[8]; + return ts_vec_angle(m_vals, other.m_vals, buf, 4); +} + +tinyspline::real +tinyspline::Vec4::distance(const Vec4 &other) const +{ + return ts_distance(m_vals, other.m_vals, 4); +} + +std::string +tinyspline::Vec4::toString() const +{ + std::ostringstream oss; + oss << "Vec4{" + << "x: " << x() + << ", y: " << y() + << ", z: " << z() + << ", w: " << w() + << "}"; + return oss.str(); +} +/*! @} */ + + + +/*! @name Frame + * + * @{ + */ +tinyspline::Frame::Frame(Vec3 &position, + Vec3 &tangent, + Vec3 &normal, + Vec3 &binormal) +: m_position(position), + m_tangent(tangent), + m_normal(normal), + m_binormal(binormal) +{} + +tinyspline::Vec3 +tinyspline::Frame::position() const +{ + return m_position; +} + +tinyspline::Vec3 +tinyspline::Frame::tangent() const +{ + return m_tangent; +} + +tinyspline::Vec3 +tinyspline::Frame::normal() const +{ + return m_normal; +} + +tinyspline::Vec3 +tinyspline::Frame::binormal() const +{ + return m_binormal; +} + +std::string +tinyspline::Frame::toString() const +{ + std::ostringstream oss; + oss << "Frame{" + << "position: " << position().toString() + << ", tangent: " << tangent().toString() + << ", normal: " << normal().toString() + << ", binormal: " << binormal().toString() + << "}"; + return oss.str(); +} +/*! @} */ + + + +/*! @name FrameSeq + * + * @{ + */ +tinyspline::FrameSeq::FrameSeq() +: m_frames(nullptr), m_size(0) +{} + +tinyspline::FrameSeq::FrameSeq(tsFrame *frames, + size_t len) +: m_frames(frames), m_size(len) +{} + +tinyspline::FrameSeq::FrameSeq(const FrameSeq &other) +: m_frames(nullptr), m_size(other.m_size) +{ + m_frames = new tsFrame[m_size]; + std::copy(other.m_frames, + other.m_frames + m_size, + m_frames); +} + +tinyspline::FrameSeq::FrameSeq(FrameSeq &&other) +: m_frames(nullptr), m_size(other.m_size) +{ + m_frames = other.m_frames; + other.m_frames = nullptr; + other.m_size = 0; +} + +tinyspline::FrameSeq::~FrameSeq() +{ + delete [] m_frames; + m_size = 0; +} + +tinyspline::FrameSeq & +tinyspline::FrameSeq::operator=(const FrameSeq &other) +{ + if (&other != this) { + tsFrame *data = new tsFrame[other.m_size]; + std::copy(other.m_frames, + other.m_frames + other.m_size, + data); + delete [] m_frames; + m_frames = data; + m_size = other.m_size; + } + return *this; +} + +tinyspline::FrameSeq & +tinyspline::FrameSeq::operator=(FrameSeq &&other) +{ + if (&other != this) { + delete [] m_frames; + m_frames = other.m_frames; + m_size = other.m_size; + other.m_frames = nullptr; + other.m_size = 0; + } + return *this; +} + +size_t +tinyspline::FrameSeq::size() const +{ + return m_size; +} + +tinyspline::Frame +tinyspline::FrameSeq::at(size_t idx) const +{ + if (idx >= m_size) + throw std::out_of_range( "idx >= size"); + tsFrame frame = m_frames[idx]; + Vec3 position = Vec3(frame.position[0], + frame.position[1], + frame.position[2]); + Vec3 tangent = Vec3(frame.tangent[0], + frame.tangent[1], + frame.tangent[2]); + Vec3 normal = Vec3(frame.normal[0], + frame.normal[1], + frame.normal[2]); + Vec3 binormal = Vec3(frame.binormal[0], + frame.binormal[1], + frame.binormal[2]); + return Frame(position, tangent, normal, binormal); +} + +std::string +tinyspline::FrameSeq::toString() const +{ + std::ostringstream oss; + oss << "FrameSeq{" + << "frames: " << size() + << "}"; + return oss.str(); +} +/*! @} */ + + + +/*! @name Domain + * + * @{ + */ +tinyspline::Domain::Domain(real min, + real max) +: m_min(min), m_max(max) +{} + +tinyspline::real +tinyspline::Domain::min() const +{ + return m_min; +} + +tinyspline::real +tinyspline::Domain::max() const +{ + return m_max; +} + +std::string +tinyspline::Domain::toString() const +{ + std::ostringstream oss; + oss << "Domain{" + << "min: " << min() + << ", max: " << max() + << "}"; + return oss.str(); +} +/*! @} */ + + + +/*! @name DeBoorNet + * + * @{ + */ +tinyspline::DeBoorNet::DeBoorNet(tsDeBoorNet &data) +: net(ts_deboornet_init()) +{ + ts_deboornet_move(&data, &net); +} + +tinyspline::DeBoorNet::DeBoorNet(const DeBoorNet &other) +: net(ts_deboornet_init()) +{ + tsStatus status; + if (ts_deboornet_copy(&other.net, &net, &status)) + throw std::runtime_error(status.message); +} + +tinyspline::DeBoorNet::DeBoorNet(DeBoorNet &&other) +: net(ts_deboornet_init()) +{ + ts_deboornet_move(&other.net, &net); +} + +tinyspline::DeBoorNet::~DeBoorNet() +{ + ts_deboornet_free(&net); +} + +tinyspline::DeBoorNet & +tinyspline::DeBoorNet::operator=(const DeBoorNet &other) +{ + if (&other != this) { + tsDeBoorNet data = ts_deboornet_init(); + tsStatus status; + if (ts_deboornet_copy(&other.net, &data, &status)) + throw std::runtime_error(status.message); + ts_deboornet_free(&net); + ts_deboornet_move(&data, &net); + } + return *this; +} + +tinyspline::DeBoorNet & +tinyspline::DeBoorNet::operator=(DeBoorNet && other) +{ + if (&other != this) { + ts_deboornet_free(&net); + ts_deboornet_move(&other.net, &net); + } + return *this; +} + +tinyspline::real +tinyspline::DeBoorNet::knot() const +{ + return ts_deboornet_knot(&net); +} + +size_t +tinyspline::DeBoorNet::index() const +{ + return ts_deboornet_index(&net); +} + +size_t +tinyspline::DeBoorNet::multiplicity() const +{ + return ts_deboornet_multiplicity(&net); +} + +size_t +tinyspline::DeBoorNet::numInsertions() const +{ + return ts_deboornet_num_insertions(&net); +} + +size_t +tinyspline::DeBoorNet::dimension() const +{ + return ts_deboornet_dimension(&net); +} + +std::vector +tinyspline::DeBoorNet::points() const +{ + const real *points = ts_deboornet_points_ptr(&net); + size_t len = ts_deboornet_len_points(&net); + return std::vector(points, points + len); +} + +std::vector +tinyspline::DeBoorNet::result() const +{ + const real *result = ts_deboornet_result_ptr(&net); + size_t len = ts_deboornet_len_result(&net); + return std::vector(result, result + len); +} + +tinyspline::Vec2 +tinyspline::DeBoorNet::resultVec2(size_t idx) const +{ + Vec4 vec4 = resultVec4(idx); + return Vec2(vec4.x(), vec4.y()); +} + +tinyspline::Vec3 +tinyspline::DeBoorNet::resultVec3(size_t idx) const +{ + Vec4 vec4 = resultVec4(idx); + return Vec3(vec4.x(), vec4.y(), vec4.z()); +} + +tinyspline::Vec4 +tinyspline::DeBoorNet::resultVec4(size_t idx) const +{ + if (idx >= ts_deboornet_num_result(&net)) + throw std::out_of_range( "idx >= num(result)"); + const real *result = ts_deboornet_result_ptr(&net); + real vals[4]; + ts_vec4_set(vals, result + idx * dimension(), dimension()); + return Vec4(vals[0], vals[1], vals[2], vals[3]); +} + +std::string +tinyspline::DeBoorNet::toString() const +{ + std::ostringstream oss; + oss << "DeBoorNet{" + << "knot: " << knot() + << ", index: " << index() + << ", multiplicity: " << multiplicity() + << ", insertions: " << numInsertions() + << ", dimension: " << dimension() + << ", points: " << ts_deboornet_num_points(&net) + << "}"; + return oss.str(); +} +/*! @} */ + + + +/****************************************************************************** +* * +* BSpline * +* * +******************************************************************************/ +tinyspline::BSpline::BSpline(tsBSpline &data) +: spline(ts_bspline_init()) +{ + ts_bspline_move(&data, &spline); +} + +tinyspline::BSpline::BSpline() +: spline(ts_bspline_init()) +{ + tsStatus status; + if (ts_bspline_new_with_control_points(1, + 3, + 0, + TS_CLAMPED, + &spline, + &status, + 0.0, 0.0, 0.0)) + throw std::runtime_error(status.message); +} + +tinyspline::BSpline::BSpline(const tinyspline::BSpline &other) +: spline(ts_bspline_init()) +{ + tsStatus status; + if (ts_bspline_copy(&other.spline, &spline, &status)) + throw std::runtime_error(status.message); +} + +tinyspline::BSpline::BSpline(BSpline &&other) +: spline(ts_bspline_init()) +{ + ts_bspline_move(&other.spline, &spline); +} + +tinyspline::BSpline::BSpline(size_t numControlPoints, + size_t dimension, + size_t degree, + Type type) +: spline(ts_bspline_init()) +{ + tsBSplineType c_type = TS_CLAMPED; + switch (type) { + case Opened: + c_type = TS_OPENED; + break; + case Clamped: + c_type = TS_CLAMPED; + break; + case Beziers: + c_type = TS_BEZIERS; + break; + default: + throw std::runtime_error("unknown type"); + } + tsStatus status; + if (ts_bspline_new(numControlPoints, + dimension, + degree, + c_type, + &spline, + &status)) + throw std::runtime_error(status.message); +} + +tinyspline::BSpline::~BSpline() +{ + ts_bspline_free(&spline); +} + +tinyspline::BSpline tinyspline::BSpline::interpolateCubicNatural( + std_real_vector_in points, size_t dimension) +{ + if (dimension == 0) + throw std::runtime_error("unsupported dimension: 0"); + if (std_real_vector_read(points)size() % dimension != 0) + throw std::runtime_error("#points % dimension != 0"); + tsBSpline data = ts_bspline_init(); + tsStatus status; + if (ts_bspline_interpolate_cubic_natural( + std_real_vector_read(points)data(), + std_real_vector_read(points)size()/dimension, + dimension, &data, &status)) + throw std::runtime_error(status.message); + return BSpline(data); +} + +tinyspline::BSpline +tinyspline::BSpline::interpolateCatmullRom(std_real_vector_in points, + size_t dimension, + real alpha, + std::vector *first, + std::vector *last, + real epsilon) +{ + if (dimension == 0) + throw std::runtime_error("unsupported dimension: 0"); + if (std_real_vector_read(points)size() % dimension != 0) + throw std::runtime_error("#points % dimension != 0"); + real *fst = nullptr; + if (first && first->size() >= dimension) + fst = first->data(); + real *lst = nullptr; + if (last && last->size() >= dimension) + lst = last->data(); + tsBSpline data = ts_bspline_init(); + tsStatus status; + if (ts_bspline_interpolate_catmull_rom( + std_real_vector_read(points)data(), + std_real_vector_read(points)size()/dimension, + dimension, alpha, fst, lst, epsilon, &data, + &status)) + throw std::runtime_error(status.message); + return BSpline(data); +} + +tinyspline::BSpline tinyspline::BSpline::parseJson(std::string json) +{ + tsBSpline data = ts_bspline_init(); + tsStatus status; + if (ts_bspline_parse_json(json.c_str(), &data, &status)) + throw std::runtime_error(status.message); + return BSpline(data); +} + +tinyspline::BSpline tinyspline::BSpline::load(std::string path) +{ + tsBSpline data = ts_bspline_init(); + tsStatus status; + if (ts_bspline_load(path.c_str(), &data, &status)) + throw std::runtime_error(status.message); + return BSpline(data); +} + +bool +tinyspline::BSpline::knotsEqual(real x, real y) +{ + return ts_knots_equal(x, y); +} + +tinyspline::BSpline & +tinyspline::BSpline::operator=(const BSpline &other) +{ + if (&other != this) { + tsBSpline data = ts_bspline_init(); + tsStatus status; + if (ts_bspline_copy(&other.spline, &data, &status)) + throw std::runtime_error(status.message); + ts_bspline_free(&spline); + ts_bspline_move(&data, &spline); + } + return *this; +} + +tinyspline::BSpline & +tinyspline::BSpline::operator=(BSpline &&other) +{ + if (&other != this) { + ts_bspline_free(&spline); + ts_bspline_move(&other.spline, &spline); + } + return *this; +} + +tinyspline::DeBoorNet tinyspline::BSpline::operator()(tinyspline::real u) const +{ + return eval(u); +} + +size_t tinyspline::BSpline::degree() const +{ + return ts_bspline_degree(&spline); +} + +size_t tinyspline::BSpline::order() const +{ + return ts_bspline_order(&spline); +} + +size_t tinyspline::BSpline::dimension() const +{ + return ts_bspline_dimension(&spline); +} + +std::vector +tinyspline::BSpline::controlPoints() const +{ + const real *ctrlps = ts_bspline_control_points_ptr(&spline); + const size_t len = ts_bspline_len_control_points(&spline); + return std::vector(ctrlps, ctrlps + len); +} + +tinyspline::Vec2 +tinyspline::BSpline::controlPointVec2At(size_t idx) const +{ + const Vec4 vec4 = controlPointVec4At(idx); + return Vec2(vec4.x(), vec4.y()); +} + +tinyspline::Vec3 +tinyspline::BSpline::controlPointVec3At(size_t idx) const +{ + const Vec4 vec4 = controlPointVec4At(idx); + return Vec3(vec4.x(), vec4.y(), vec4.z()); +} + +tinyspline::Vec4 +tinyspline::BSpline::controlPointVec4At(size_t idx) const +{ + const real *ctrlp; + tsStatus status; + if (ts_bspline_control_point_at_ptr(&spline, + idx, + &ctrlp, + &status)) + throw std::runtime_error(status.message); + real vals[4]; + ts_vec4_set(vals, ctrlp, dimension()); + return Vec4(vals[0], vals[1], vals[2], vals[3]); +} + +std::vector +tinyspline::BSpline::knots() const +{ + const real *knots = ts_bspline_knots_ptr(&spline); + size_t num = ts_bspline_num_knots(&spline); + return std::vector(knots, knots + num); +} + +tinyspline::real tinyspline::BSpline::knotAt(size_t index) const +{ + real knot; + tsStatus status; + if (ts_bspline_knot_at(&spline, index, &knot, &status)) + throw std::runtime_error(status.message); + return knot; +} + +size_t tinyspline::BSpline::numControlPoints() const +{ + return ts_bspline_num_control_points(&spline); +} + +tinyspline::DeBoorNet tinyspline::BSpline::eval(tinyspline::real u) const +{ + tsDeBoorNet net = ts_deboornet_init(); + tsStatus status; + if (ts_bspline_eval(&spline, u, &net, &status)) + throw std::runtime_error(status.message); + return tinyspline::DeBoorNet(net); +} + +tinyspline::std_real_vector_out +tinyspline::BSpline::evalAll(std_real_vector_in knots) const +{ + const size_t num_knots = std_real_vector_read(knots)size(); + const real *knots_ptr = std_real_vector_read(knots)data(); + tinyspline::real *points; + tsStatus status; + if (ts_bspline_eval_all(&spline, + knots_ptr, + num_knots, + &points, + &status)) { + throw std::runtime_error(status.message); + } + real *first = points; + real *last = first + num_knots * dimension(); + std_real_vector_init(vec)(first, last); + std::free(points); + return vec; +} + +tinyspline::std_real_vector_out +tinyspline::BSpline::sample(size_t num) const +{ + tinyspline::real *points; + size_t actualNum; + tsStatus status; + if (ts_bspline_sample(&spline, + num, + &points, + &actualNum, + &status)) { + throw std::runtime_error(status.message); + } + real *first = points; + real *last = first + actualNum * dimension(); + std_real_vector_init(vec)(first, last); + std::free(points); + return vec; +} + +tinyspline::DeBoorNet tinyspline::BSpline::bisect(real value, + real epsilon, + bool persnickety, + size_t index, + bool ascending, + size_t maxIter) const +{ + tsDeBoorNet net = ts_deboornet_init(); + tsStatus status; + if (ts_bspline_bisect(&spline, + value, + epsilon, + persnickety, + index, + ascending, + maxIter, + &net, + &status)) + throw std::runtime_error(status.message); + return DeBoorNet(net); +} + +tinyspline::Domain tinyspline::BSpline::domain() const +{ + real min, max; + ts_bspline_domain(&spline, &min, &max); + return Domain(min, max); +} + +bool tinyspline::BSpline::isClosed(tinyspline::real epsilon) const +{ + int closed = 0; + tsStatus status; + if (ts_bspline_is_closed(&spline, epsilon, &closed, &status)) + throw std::runtime_error(status.message); + return closed == 1; +} + +tinyspline::FrameSeq +tinyspline::BSpline::computeRMF(std_real_vector_in knots, + tinyspline::Vec3 *firstNormal) const +{ + tsStatus status; + size_t num = std_real_vector_read(knots)size(); + const real *knots_ptr = std_real_vector_read(knots)data(); + tsFrame *frames = new tsFrame[num]; + if (firstNormal && num > 0) { + ts_vec3_init(frames[0].normal, + firstNormal->x(), + firstNormal->y(), + firstNormal->z()); + } + if (ts_bspline_compute_rmf(&spline, + knots_ptr, + num, + firstNormal != nullptr, + frames, + &status)) + throw std::runtime_error(status.message); + FrameSeq seq = FrameSeq(frames, num); + return seq; +} + + +tinyspline::BSpline +tinyspline::BSpline::subSpline(real knot0, real knot1) const +{ + tsBSpline data = ts_bspline_init(); + tsStatus status; + if (ts_bspline_sub_spline(&spline, + knot0, + knot1, + &data, + &status)) + throw std::runtime_error(status.message); + return BSpline(data); +} + +tinyspline::std_real_vector_out +tinyspline::BSpline::uniformKnotSeq(size_t num) const +{ + std_real_vector_init(knots)(num); + real *knots_ptr = std_real_vector_read(knots)data(); + ts_bspline_uniform_knot_seq(&spline, num, knots_ptr); + return knots; +} + +tinyspline::std_real_vector_out +tinyspline::BSpline::equidistantKnotSeq(size_t num, + size_t numSamples) const +{ + tsStatus status; + std_real_vector_init(knots)(num); + real *knots_ptr = std_real_vector_read(knots)data(); + if (ts_bspline_equidistant_knot_seq(&spline, + num, + knots_ptr, + numSamples, + &status)) + throw std::runtime_error(status.message); + return knots; +} + +tinyspline::ChordLengths +tinyspline::BSpline::chordLengths(std_real_vector_in knots) const +{ + tsStatus status; + size_t num = std_real_vector_read(knots)size(); + real *knotsArr = new real[num]; + real *lengths = new real[num]; + std::copy(std_real_vector_read(knots)begin(), + std_real_vector_read(knots)end(), + knotsArr); + if (ts_bspline_chord_lengths(&spline, + knotsArr, + num, + lengths, + &status)) + throw std::runtime_error(status.message); + return ChordLengths(*this, knotsArr, lengths, num); +} + +tinyspline::ChordLengths +tinyspline::BSpline::chordLengths(size_t numSamples) const +{ + return chordLengths(uniformKnotSeq(numSamples)); +} + +std::string tinyspline::BSpline::toJson() const +{ + char *json; + tsStatus status; + if (ts_bspline_to_json(&spline, &json, &status)) + throw std::runtime_error(status.message); + std::string string(json); + std::free(json); + return string; +} + +void tinyspline::BSpline::save(std::string path) const +{ + tsStatus status; + if (ts_bspline_save(&spline, path.c_str(), &status)) + throw std::runtime_error(status.message); +} + +void tinyspline::BSpline::setControlPoints( + const std::vector &ctrlp) +{ + size_t expected = ts_bspline_len_control_points(&spline); + size_t actual = ctrlp.size(); + if (expected != actual) { + std::ostringstream oss; + oss << "Expected size: " << expected + << ", Actual size: " << actual; + throw std::runtime_error(oss.str()); + } + tsStatus status; + if (ts_bspline_set_control_points(&spline, ctrlp.data(), &status)) + throw std::runtime_error(status.message); +} + +void +tinyspline::BSpline::setControlPointVec2At(size_t idx, Vec2 &cp) +{ + Vec4 vec4(cp.x(), cp.y(), (real) 0.0, (real) 0.0); + setControlPointVec4At(idx, vec4); +} + +void +tinyspline::BSpline::setControlPointVec3At(size_t idx, Vec3 &cp) +{ + Vec4 vec4(cp.x(), cp.y(), cp.z(), (real) 0.0); + setControlPointVec4At(idx, vec4); +} + +void +tinyspline::BSpline::setControlPointVec4At(size_t idx, Vec4 &cp) +{ + std::vector vals(dimension()); + for (size_t i = 0; i < vals.size(); i++) + vals[i] = (real) 0.0; + if (vals.size() >= 4) vals[3] = cp.w(); + if (vals.size() >= 3) vals[2] = cp.z(); + if (vals.size() >= 2) vals[1] = cp.y(); + if (vals.size() >= 1) vals[0] = cp.x(); + tsStatus status; + if (ts_bspline_set_control_point_at(&spline, + idx, + vals.data(), + &status)) + throw std::runtime_error(status.message); +} + +void +tinyspline::BSpline::setKnots(const std::vector &knots) +{ + size_t expected = ts_bspline_num_knots(&spline); + size_t actual = knots.size(); + if (expected != actual) { + std::ostringstream oss; + oss << "Expected size: " << expected + << ", Actual size: " << actual; + throw std::runtime_error(oss.str()); + } + tsStatus status; + if (ts_bspline_set_knots(&spline, + knots.data(), + &status)) + throw std::runtime_error(status.message); +} + +void tinyspline::BSpline::setKnotAt(size_t index, tinyspline::real knot) +{ + tsStatus status; + if (ts_bspline_set_knot_at(&spline, index, knot, &status)) + throw std::runtime_error(status.message); +} + +tinyspline::BSpline tinyspline::BSpline::insertKnot(tinyspline::real u, + size_t n) const +{ + tsBSpline data = ts_bspline_init(); + size_t k; + tsStatus status; + if (ts_bspline_insert_knot(&spline, u, n, &data, &k, &status)) + throw std::runtime_error(status.message); + return BSpline(data); +} + +tinyspline::BSpline tinyspline::BSpline::split(tinyspline::real u) const +{ + tsBSpline data = ts_bspline_init(); + size_t k; + tsStatus status; + if (ts_bspline_split(&spline, u, &data, &k, &status)) + throw std::runtime_error(status.message); + return BSpline(data); +} + +tinyspline::BSpline tinyspline::BSpline::tension( + tinyspline::real tension) const +{ + tsBSpline data = ts_bspline_init(); + tsStatus status; + if (ts_bspline_tension(&spline, tension, &data, &status)) + throw std::runtime_error(status.message); + return BSpline(data); +} + +tinyspline::BSpline tinyspline::BSpline::toBeziers() const +{ + tsBSpline data = ts_bspline_init(); + tsStatus status; + if (ts_bspline_to_beziers(&spline, &data, &status)) + throw std::runtime_error(status.message); + return BSpline(data); +} + +tinyspline::BSpline tinyspline::BSpline::derive(size_t n, real epsilon) const +{ + tsBSpline data = ts_bspline_init(); + tsStatus status; + if (ts_bspline_derive(&spline, n, epsilon, &data, &status)) + throw std::runtime_error(status.message); + return BSpline(data); +} + +tinyspline::BSpline tinyspline::BSpline::elevateDegree( + size_t amount, real epsilon) const +{ + tsBSpline data = ts_bspline_init(); + tsStatus status; + if (ts_bspline_elevate_degree(&spline, amount, epsilon, + &data, &status)) { + throw std::runtime_error(status.message); + } + return BSpline(data); +} + +tinyspline::BSpline tinyspline::BSpline::alignWith( + const BSpline &other, BSpline &otherAligned, real epsilon) const +{ + tsBSpline data = ts_bspline_init(); + tsBSpline deleteIf_Other_And_OtherAligned_AreDifferent = + otherAligned.spline; + tsStatus status; + if (ts_bspline_align(&spline, &other.spline, epsilon, &data, + &otherAligned.spline, &status)) { + throw std::runtime_error(status.message); + } + if (&other != &otherAligned) + ts_bspline_free(&deleteIf_Other_And_OtherAligned_AreDifferent); + return BSpline(data); +} + +tinyspline::Morphism tinyspline::BSpline::morphTo( + const BSpline &other, real epsilon) const +{ + return Morphism(*this, other, epsilon); +} + +std::string tinyspline::BSpline::toString() const +{ + Domain d = domain(); + std::ostringstream oss; + oss << "BSpline{" + << "dimension: " << dimension() + << ", degree: " << degree() + << ", domain: [" << d.min() << ", " << d.max() << "]" + << ", control points: " << numControlPoints() + << ", knots: " << ts_bspline_num_knots(&spline) + << "}"; + return oss.str(); +} + + + +/*! @name Morphism + * + * @{ + */ +tinyspline::Morphism::Morphism(const BSpline &origin, + const BSpline &target, + real epsilon) +: m_origin(origin), m_target(target), m_epsilon(epsilon) +{ + m_originAligned = origin.alignWith(target, m_targetAligned, epsilon); + // Make buffer compatible by copying one of the aligned splines. + m_buffer = m_originAligned; +} + +tinyspline::BSpline +tinyspline::Morphism::eval(real t) +{ + tsStatus status; + if (t <= 0) return m_origin; + if (t >= 1) return m_target; + if (ts_bspline_morph(&m_originAligned.spline, + &m_targetAligned.spline, + t, m_epsilon, + &m_buffer.spline, &status)) { + throw std::runtime_error(status.message); + } + return m_buffer; +} + +tinyspline::BSpline +tinyspline::Morphism::origin() const +{ + return m_origin; +} + +tinyspline::BSpline +tinyspline::Morphism::target() const +{ + return m_target; +} + +tinyspline::real +tinyspline::Morphism::epsilon() const +{ + return m_epsilon; +} + +tinyspline::BSpline +tinyspline::Morphism::operator()(real t) +{ + return eval(t); +} + +std::string tinyspline::Morphism::toString() const +{ + std::ostringstream oss; + oss << "Morphism{" + << "buffer: " << m_buffer.toString() + << ", epsilon: " << epsilon() + << "}"; + return oss.str(); +} +/*! @} */ + + + +/*! @name ChordLenghts + * @{ + */ +tinyspline::ChordLengths::ChordLengths() +: m_spline(), + m_knots(nullptr), + m_chordLengths(nullptr), + m_size(0) +{} + +tinyspline::ChordLengths::ChordLengths(const BSpline &spline, + real *knots, + real *chordLengths, + size_t size) +: m_spline(spline), + m_knots(knots), + m_chordLengths(chordLengths), + m_size(size) +{} + +tinyspline::ChordLengths::ChordLengths(const ChordLengths &other) +: m_spline(other.m_spline), + m_knots(nullptr), + m_chordLengths(nullptr), + m_size(other.m_size) +{ + m_knots = new real[m_size]; + std::copy(other.m_knots, + other.m_knots + m_size, + m_knots); + m_chordLengths = new real[m_size]; + std::copy(other.m_chordLengths, + other.m_chordLengths + m_size, + m_chordLengths); +} + +tinyspline::ChordLengths::ChordLengths(ChordLengths &&other) +: m_spline(), + m_knots(nullptr), + m_chordLengths(nullptr), + m_size(0) +{ + *this = std::move(other); +} + +tinyspline::ChordLengths::~ChordLengths() +{ + delete [] m_knots; + delete [] m_chordLengths; + m_size = 0; +} + +tinyspline::ChordLengths & +tinyspline::ChordLengths::operator=(const ChordLengths &other) +{ + if (&other != this) { + real *knots = new real[other.m_size]; + std::copy(other.m_knots, + other.m_knots + other.m_size, + knots); + real *chordLengths = new real[other.m_size]; + std::copy(other.m_chordLengths, + other.m_chordLengths + other.m_size, + chordLengths); + delete [] m_knots; + delete [] m_chordLengths; + m_spline = other.m_spline; + m_knots = knots; + m_chordLengths = chordLengths; + m_size = other.m_size; + } + return *this; +} + +tinyspline::ChordLengths & +tinyspline::ChordLengths::operator=(ChordLengths &&other) +{ + if (&other != this) { + delete [] m_knots; + delete [] m_chordLengths; + m_spline = other.m_spline; + m_knots = other.m_knots; + m_chordLengths = other.m_chordLengths; + m_size = other.m_size; + other.m_spline = BSpline(); + other.m_knots = nullptr; + other.m_chordLengths = nullptr; + other.m_size = 0; + } + return *this; +} + +tinyspline::BSpline +tinyspline::ChordLengths::spline() const +{ + return m_spline; +} + +std::vector +tinyspline::ChordLengths::knots() const +{ + return std::vector(m_knots, + m_knots + m_size); +} + +std::vector +tinyspline::ChordLengths::values() const +{ + return std::vector(m_chordLengths, + m_chordLengths + m_size); +} + +size_t +tinyspline::ChordLengths::size() const +{ + return m_size; +} + +tinyspline::real +tinyspline::ChordLengths::arcLength() const +{ + return m_size == 0 ? 0 : m_chordLengths[m_size - 1]; +} + +tinyspline::real +tinyspline::ChordLengths::lengthToKnot(real len) const +{ + tsStatus status; + real knot; + if (ts_chord_lengths_length_to_knot(m_knots, + m_chordLengths, + m_size, + len, + &knot, + &status)) + throw std::runtime_error(status.message); + return knot; +} + +tinyspline::real +tinyspline::ChordLengths::tToKnot(real t) const +{ + tsStatus status; + real knot; + if (ts_chord_lengths_t_to_knot(m_knots, + m_chordLengths, + m_size, + t, + &knot, + &status)) + throw std::runtime_error(status.message); + return knot; +} + +std::string +tinyspline::ChordLengths::toString() const +{ + std::ostringstream oss; + oss << "ChordLengths{" + << "spline: " << m_spline.toString() + << ", values: " << m_size + << "}"; + return oss.str(); +} +/*! @} */ + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/thirdparty/tinyspline_lib/tinysplinecxx.h b/thirdparty/tinyspline_lib/tinysplinecxx.h new file mode 100644 index 0000000000..bd810f5b78 --- /dev/null +++ b/thirdparty/tinyspline_lib/tinysplinecxx.h @@ -0,0 +1,960 @@ +/** @file */ + +#pragma once + +#include "tinyspline.h" +#include +#include + +#ifndef TINYSPLINECXX_API +#define TINYSPLINECXX_API TINYSPLINE_API +#endif + + + +/*! @name Emscripten Extensions + * + * Please see the following references for more details on how to use + * Emscripten with C++: + * + * https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html + * https://emscripten.org/docs/api_reference/bind.h.html + * + * @{ + */ +#ifdef TINYSPLINE_EMSCRIPTEN + +// Additional includes and namespaces. +#include +#include +using namespace emscripten; + +/* Used by value_objects to guard read-only properties. */ +void inline +cannotWrite() +{ throw std::runtime_error("cannot write read-only property"); } + +/* Allow clients to read exception messages in the Javascript binding. */ +std::string +exceptionMessage(int ptr) +{ return std::string(reinterpret_cast(ptr)->what()); } + +// Map: std::vector <--> JS array +// https://github.com/emscripten-core/emscripten/issues/11070#issuecomment-717675128 +namespace emscripten { +namespace internal { +template +struct BindingType> { + using ValBinding = BindingType; + using WireType = ValBinding::WireType; + + static WireType + toWireType(const std::vector &vec) + { return ValBinding::toWireType(val::array(vec)); } + + static std::vector + fromWireType(WireType value) + { return vecFromJSArray(ValBinding::fromWireType(value)); } +}; + +template +struct TypeID::type, + std::vector::type::value_type, + typename Canonicalized::type::allocator_type>>::value>> { + static constexpr TYPEID + get() { return TypeID::get(); } +}; +} // namespace internal +} // namespace emscripten +#endif +/*! @} */ + + + +namespace tinyspline { + +/*! @name API Configuration + * + * @{ + */ +typedef tsReal real; +/*! @} */ + + + +/*! @name Swig Type Mapping + * + * Methods that do not return or set the value of a class attribute (let's call + * these methods `non-accessor methods') must return/take instances of + * std::vector as pointer. Otherwise, they aren't type mapped by Swig to the + * std::vector representation of the target language. + * + * @{ + */ +#ifdef SWIG +using std_real_vector_in = std::vector *; +using std_real_vector_out = std::vector *; +#else +using std_real_vector_in = const std::vector &; +using std_real_vector_out = std::vector; +#endif +/*! @} */ + + + +/*! @name Vector Math + * + * Wrapper classes for TinySpline's vector math. + * + * @{ + */ +class TINYSPLINECXX_API Vec2 { +public: + Vec2(); + Vec2(real x, real y); + + Vec2 operator+(const Vec2 &other); + Vec2 operator-(const Vec2 &other); + Vec2 operator*(real scalar); + + real x() const; + void setX(real val); + real y() const; + void setY(real val); + std::vector values() const; + + Vec2 add(const Vec2 &other) const; + Vec2 subtract(const Vec2 &other) const; + Vec2 multiply(real scalar) const; + Vec2 normalize() const; + TS_DEPRECATED Vec2 norm() const; + real magnitude() const; + real dot(const Vec2 &other) const; + real angle(const Vec2 &other) const; + real distance(const Vec2 &other) const; + + std::string toString() const; + +private: + real m_vals[2]; + +#ifdef TINYSPLINE_EMSCRIPTEN +public: + void setValues(std::vector) { cannotWrite(); } +#endif +}; + +class TINYSPLINECXX_API Vec3 { +public: + Vec3(); + Vec3(real x, real y, real z); + + Vec3 operator+(const Vec3 &other); + Vec3 operator-(const Vec3 &other); + Vec3 operator*(real scalar); + + real x() const; + void setX(real val); + real y() const; + void setY(real val); + real z() const; + void setZ(real val); + std::vector values() const; + + Vec3 add(const Vec3 &other) const; + Vec3 subtract(const Vec3 &other) const; + Vec3 multiply(real scalar) const; + Vec3 cross(const Vec3 &other) const; + Vec3 normalize() const; + TS_DEPRECATED Vec3 norm() const; + real magnitude() const; + real dot(const Vec3 &other) const; + real angle(const Vec3 &other) const; + real distance(const Vec3 &other) const; + + std::string toString() const; + +private: + real m_vals[3]; + +#ifdef TINYSPLINE_EMSCRIPTEN +public: + void setValues(std::vector) { cannotWrite(); } +#endif +}; + +class TINYSPLINECXX_API Vec4 { +public: + Vec4(); + Vec4(real x, real y, real z, real w); + + Vec4 operator+(const Vec4 &other); + Vec4 operator-(const Vec4 &other); + Vec4 operator*(real scalar); + + real x() const; + void setX(real val); + real y() const; + void setY(real val); + real z() const; + void setZ(real val); + real w() const; + void setW(real val); + std::vector values() const; + + Vec4 add(const Vec4 &other) const; + Vec4 subtract(const Vec4 &other) const; + Vec4 multiply(real scalar) const; + Vec4 normalize() const; + TS_DEPRECATED Vec4 norm() const; + real magnitude() const; + real dot(const Vec4 &other) const; + real angle(const Vec4 &other) const; + real distance(const Vec4 &other) const; + + std::string toString() const; + +private: + real m_vals[4]; + +#ifdef TINYSPLINE_EMSCRIPTEN +public: + void setValues(std::vector) { cannotWrite(); } +#endif +}; + +#ifdef TINYSPLINE_EMSCRIPTEN +class VecMath { +public: + /*! @name Add + * + * @{ + */ + static Vec2 + add2(const Vec2 &a, const Vec2 &b) + { return a.add(b); } + + static Vec3 + add3(const Vec3 &a, const Vec3 &b) + { return a.add(b); } + + static Vec4 + add4(const Vec4 &a, const Vec4 &b) + { return a.add(b); } + /*! @} */ + + /*! @name Subtract + * + * @{ + */ + static Vec2 + subtract2(const Vec2 &a, const Vec2 &b) + { return a.subtract(b); } + + static Vec3 + subtract3(const Vec3 &a, const Vec3 &b) + { return a.subtract(b); } + + static Vec4 + subtract4(const Vec4 &a, const Vec4 &b) + { return a.subtract(b); } + /*! @} */ + + /*! @name Multiply + * + * @{ + */ + static Vec2 + multiply2(const Vec2 &vec, real scalar) + { return vec.multiply(scalar); } + + static Vec3 + multiply3(const Vec3 &vec, real scalar) + { return vec.multiply(scalar); } + + static Vec4 + multiply4(const Vec4 &vec, real scalar) + { return vec.multiply(scalar); } + /*! @} */ + + /*! @name Cross + * + * @{ + */ + static Vec3 + cross3(const Vec3 &a, Vec3 &b) + { return a.cross(b); } + /*! @} */ + + /*! @name Normalize + * + * @{ + */ + static Vec2 + normalize2(const Vec2 &vec) + { return vec.normalize(); } + + static Vec3 + normalize3(const Vec3 &vec) + { return vec.normalize(); } + + static Vec4 + normalize4(const Vec4 &vec) + { return vec.normalize(); } + /*! @} */ + + /*! @name Magnitude + * + * @{ + */ + static real + magnitude2(const Vec2 &vec) + { return vec.magnitude(); } + + static real + magnitude3(const Vec3 &vec) + { return vec.magnitude(); } + + static real + magnitude4(const Vec4 &vec) + { return vec.magnitude(); } + /*! @} */ + + /*! @name Dot + * + * @{ + */ + static real + dot2(const Vec2 &a, Vec2 &b) + { return a.dot(b); } + + static real + dot3(const Vec3 &a, Vec3 &b) + { return a.dot(b); } + + static real + dot4(const Vec4 &a, Vec4 &b) + { return a.dot(b); } + /*! @} */ + + /*! @name Angle + * + * @{ + */ + static real + angle2(const Vec2 &a, Vec2 &b) + { return a.angle(b); } + + static real + angle3(const Vec3 &a, Vec3 &b) + { return a.angle(b); } + + static real + angle4(const Vec4 &a, Vec4 &b) + { return a.angle(b); } + /*! @} */ + + /*! @name Distance + * + * @{ + */ + static real + distance2(const Vec2 &a, Vec2 &b) + { return a.distance(b); } + + static real + distance3(const Vec3 &a, Vec3 &b) + { return a.distance(b); } + + static real + distance4(const Vec4 &a, Vec4 &b) + { return a.distance(b); } + /*! @} */ +}; +#endif +/*! @} */ + + + +/*! @name Spline Framing + * + * Object-oriented representation of ::tsFrame (::Frame) and sequences of + * ::tsFrame (::FrameSeq). Instances of ::FrameSeq are created by + * ::BSpline::computeRMF. + * + * @{ + */ +class TINYSPLINECXX_API Frame { +public: + Frame(Vec3 &position, + Vec3 &tangent, + Vec3 &normal, + Vec3 &binormal); + + Vec3 position() const; + Vec3 tangent() const; + Vec3 normal() const; + Vec3 binormal() const; + + std::string toString() const; + +private: + Vec3 m_position, + m_tangent, + m_normal, + m_binormal; + +#ifdef TINYSPLINE_EMSCRIPTEN +public: + Frame() {} + void setPosition(Vec3) { cannotWrite(); } + void setTangent(Vec3) { cannotWrite(); } + void setNormal(Vec3) { cannotWrite(); } + void setBinormal(Vec3) { cannotWrite(); } +#endif +}; + +class TINYSPLINECXX_API FrameSeq { +public: + FrameSeq(); + FrameSeq(const FrameSeq &other); + FrameSeq(FrameSeq &&other); + virtual ~FrameSeq(); + + FrameSeq &operator=(const FrameSeq &other); + FrameSeq &operator=(FrameSeq &&other); + + size_t size() const; + Frame at(size_t idx) const; + + std::string toString() const; + +private: + tsFrame *m_frames; + size_t m_size; + FrameSeq(tsFrame *frames, + size_t len); + friend class BSpline; +}; +/*! @} */ + + + +/*! @name Utility Classes + * + * Little helper classes, such as value classes or classes with only static + * methods. These are primarily classes that do not really fit into another + * group or are too small to form their own group. + * + * @{ + */ +class TINYSPLINECXX_API Domain { +public: + /* Constructors & Destructors */ + Domain(real min, real max); + + /* Accessors */ + real min() const; + real max() const; + + /* Debug */ + std::string toString() const; + +private: + real m_min, + m_max; + +#ifdef TINYSPLINE_EMSCRIPTEN +public: + Domain() + : Domain(TS_DOMAIN_DEFAULT_MIN, TS_DOMAIN_DEFAULT_MAX) + {} + void setMin(real) { cannotWrite(); } + void setMax(real) { cannotWrite(); } +#endif +}; +/*! @} */ + + + +class TINYSPLINECXX_API DeBoorNet { +public: + /* Constructors & Destructors */ + DeBoorNet(const DeBoorNet &other); + DeBoorNet(DeBoorNet &&other); + virtual ~DeBoorNet(); + + /* Operators */ + DeBoorNet & operator=(const DeBoorNet &other); + DeBoorNet & operator=(DeBoorNet &&other); + + /* Accessors */ + real knot() const; + size_t index() const; + size_t multiplicity() const; + size_t numInsertions() const; + size_t dimension() const; + std::vector points() const; + std::vector result() const; + + /** + * Returns the result at \p idx as ::Vec2. Note that, by design, \p idx + * cannot be greater than \c 1. It is safe to call this method even if + * ::dimension is less than \c 2. In this case, the missing components + * are set to \c 0. If ::dimension is greater than \c 2, the excess + * values are ignored. + * + * @return + * The result at \p idx as ::Vec2. + * @throws std::out_of_range + * If \p idx is greater than \c 1, or if \p idx is \c 1, but there + * is only one result. + */ + Vec2 resultVec2(size_t idx = 0) const; + + /** + * Returns the result at \p idx as ::Vec3. Note that, by design, \p idx + * cannot be greater than \c 1. It is safe to call this method even if + * ::dimension is less than \c 3. In this case, the missing components + * are set to \c 0. If ::dimension is greater than \c 3, the excess + * values are ignored. + * + * @return + * The result at \p idx as ::Vec3. + * @throws std::out_of_range + * If \p idx is greater than \c 1, or if \p idx is \c 1, but there + * is only one result. + */ + Vec3 resultVec3(size_t idx = 0) const; + + /** + * Returns the result at \p idx as ::Vec4. Note that, by design, \p idx + * cannot be greater than \c 1. It is safe to call this method even if + * ::dimension is less than \c 4. In this case, the missing components + * are set to \c 0. If ::dimension is greater than \c 4, the excess + * values are ignored. + * + * @return + * The result at \p idx as ::Vec4. + * @throws std::out_of_range + * If \p idx is greater than \c 1, or if \p idx is \c 1, but there + * is only one result. + */ + Vec4 resultVec4(size_t idx = 0) const; + + /* Debug */ + std::string toString() const; + +private: + tsDeBoorNet net; + + /* Constructors & Destructors */ + explicit DeBoorNet(tsDeBoorNet &data); + + friend class BSpline; + +#ifdef TINYSPLINE_EMSCRIPTEN +public: + DeBoorNet() : net(ts_deboornet_init()) {} + void setKnot(real) { cannotWrite(); } + void setIndex(size_t) { cannotWrite(); } + void setMultiplicity(size_t) { cannotWrite(); } + void setNumInsertions(size_t) { cannotWrite(); } + void setDimension(size_t) { cannotWrite(); } + void setPoints(std::vector) { cannotWrite(); } + void setResult(std::vector) { cannotWrite(); } +#endif +}; + + + +class Morphism; +class ChordLengths; +class TINYSPLINECXX_API BSpline { +public: + enum Type { Opened, Clamped, Beziers }; + + /* Constructors & Destructors */ + BSpline(); + BSpline(const BSpline &other); + BSpline(BSpline &&other); + explicit BSpline(size_t numControlPoints, + size_t dimension = 2, + size_t degree = 3, + Type type = Type::Clamped); + virtual ~BSpline(); + + /* Create from static method */ + static BSpline interpolateCubicNatural(std_real_vector_in points, + size_t dimension); + static BSpline interpolateCatmullRom(std_real_vector_in points, + size_t dimension, + real alpha = (real) 0.5, + std::vector *first = nullptr, + std::vector *last = nullptr, + real epsilon = TS_POINT_EPSILON); + static BSpline parseJson(std::string json); + static BSpline load(std::string path); + + static bool knotsEqual(real x, real y); + + /* Operators */ + BSpline & operator=(const BSpline &other); + BSpline & operator=(BSpline &&other); + DeBoorNet operator()(real u) const; + + /* Accessors */ + size_t degree() const; + size_t order() const; + size_t dimension() const; + std::vector controlPoints() const; + Vec2 controlPointVec2At(size_t idx) const; + Vec3 controlPointVec3At(size_t idx) const; + Vec4 controlPointVec4At(size_t idx) const; + std::vector knots() const; + real knotAt(size_t index) const; + + /* Query */ + size_t numControlPoints() const; + DeBoorNet eval(real u) const; + std_real_vector_out evalAll(std_real_vector_in knots) const; + std_real_vector_out sample(size_t num = 0) const; + DeBoorNet bisect(real value, + real epsilon = (real) 0.0, + bool persnickety = false, + size_t index = 0, + bool ascending = true, + size_t maxIter = 50) const; + Domain domain() const; + bool isClosed(real epsilon = TS_POINT_EPSILON) const; + FrameSeq computeRMF(std_real_vector_in knots, + Vec3 *firstNormal = nullptr) const; + BSpline subSpline(real knot0, real knot1) const; + std_real_vector_out uniformKnotSeq(size_t num = 100) const; + std_real_vector_out equidistantKnotSeq(size_t num = 100, + size_t numSamples = 0) const; + ChordLengths chordLengths(std_real_vector_in knots) const; + ChordLengths chordLengths(size_t numSamples = 200) const; + + /* Serialization */ + std::string toJson() const; + void save(std::string path) const; + + /* Modifications */ + void setControlPoints(const std::vector &ctrlp); + void setControlPointVec2At(size_t idx, Vec2 &cp); + void setControlPointVec3At(size_t idx, Vec3 &cp); + void setControlPointVec4At(size_t idx, Vec4 &cp); + void setKnots(const std::vector &knots); + void setKnotAt(size_t index, real knot); + + /* Transformations */ + BSpline insertKnot(real u, size_t n) const; + BSpline split(real u) const; + BSpline tension(real tension) const; + BSpline toBeziers() const; + BSpline derive(size_t n = 1, + real epsilon = TS_POINT_EPSILON) const; + BSpline elevateDegree(size_t amount, + real epsilon = TS_POINT_EPSILON) const; + BSpline alignWith(const BSpline &other, BSpline &otherAligned, + real epsilon = TS_POINT_EPSILON) const; + Morphism morphTo(const BSpline &other, + real epsilon = TS_POINT_EPSILON) const; + + /* Debug */ + std::string toString() const; + +private: + tsBSpline spline; + + /* Constructors & Destructors */ + explicit BSpline(tsBSpline &data); + + /* Needs to access ::spline. */ + friend class Morphism; + +#ifdef TINYSPLINE_EMSCRIPTEN +public: + std_real_vector_out sample0() const { return sample(); } + std_real_vector_out sample1(size_t num) const { return sample(num); } + BSpline derive0() const { return derive(); } + BSpline derive1(size_t n) const { return derive(n); } + BSpline derive2(size_t n, real eps) const { return derive(n, eps); } +#endif +}; + + + +/*! @name Spline Morphing + * + * @{ + */ +class TINYSPLINECXX_API Morphism { +public: + Morphism(const BSpline &origin, + const BSpline &target, + real epsilon = TS_POINT_EPSILON); + + BSpline origin() const; + BSpline target() const; + real epsilon() const; + + BSpline eval(real t); + BSpline operator()(real t); + + std::string toString() const; +private: + BSpline m_origin, m_target; + real m_epsilon; + BSpline m_originAligned, m_targetAligned; + BSpline m_buffer; +}; +/*! @} */ + + + +/*! @name Reparametrization by Arc Length + * @{ + */ +class TINYSPLINECXX_API ChordLengths { +public: + ChordLengths(); + ChordLengths(const ChordLengths &other); + ChordLengths(ChordLengths &&other); + virtual ~ChordLengths(); + + ChordLengths &operator=(const ChordLengths &other); + ChordLengths &operator=(ChordLengths &&other); + + BSpline spline() const; + std::vector knots() const; + std::vector values() const; + size_t size() const; + + real arcLength() const; + real lengthToKnot(real len) const; + real tToKnot(real t) const; + + std::string toString() const; +private: + BSpline m_spline; + real *m_knots, *m_chordLengths; + size_t m_size; + ChordLengths(const BSpline &spline, + real *knots, + real *chordLengths, + size_t size); + friend class BSpline; +}; +/*! @} */ + +} + + + +#ifdef TINYSPLINE_EMSCRIPTEN +using namespace tinyspline; +EMSCRIPTEN_BINDINGS(tinyspline) { + function("exceptionMessage", &exceptionMessage); + + // Vector Math + value_object("Vec2") + .field("x", + &Vec2::x, + &Vec2::setX) + .field("y", + &Vec2::y, + &Vec2::setY) + .field("values", + &Vec2::values, + &Vec2::setValues) + ; + value_object("Vec3") + .field("x", + &Vec3::x, + &Vec3::setX) + .field("y", + &Vec3::y, + &Vec3::setY) + .field("z", + &Vec3::z, + &Vec3::setZ) + .field("values", + &Vec3::values, + &Vec3::setValues) + ; + value_object("Vec4") + .field("x", + &Vec4::x, + &Vec4::setX) + .field("y", + &Vec4::y, + &Vec4::setY) + .field("z", + &Vec4::z, + &Vec4::setZ) + .field("w", + &Vec4::w, + &Vec4::setW) + .field("values", + &Vec4::values, + &Vec4::setValues) + ; + class_("VecMath") + .class_function("add", &VecMath::add2) + .class_function("add", &VecMath::add3) + .class_function("add", &VecMath::add4) + .class_function("subtract", &VecMath::subtract2) + .class_function("subtract", &VecMath::subtract3) + .class_function("subtract", &VecMath::subtract4) + .class_function("multiply", &VecMath::multiply2) + .class_function("multiply", &VecMath::multiply3) + .class_function("multiply", &VecMath::multiply4) + .class_function("cross", &VecMath::cross3) + .class_function("normalize", &VecMath::normalize2) + .class_function("normalize", &VecMath::normalize3) + .class_function("normalize", &VecMath::normalize4) + .class_function("magnitude", &VecMath::magnitude2) + .class_function("magnitude", &VecMath::magnitude3) + .class_function("magnitude", &VecMath::magnitude4) + .class_function("dot", &VecMath::dot2) + .class_function("dot", &VecMath::dot3) + .class_function("dot", &VecMath::dot4) + .class_function("angle", &VecMath::angle2) + .class_function("angle", &VecMath::angle3) + .class_function("angle", &VecMath::angle4) + .class_function("distance", &VecMath::distance2) + .class_function("distance", &VecMath::distance3) + .class_function("distance", &VecMath::distance4) + ; + + // Spline Framing + value_object("Frame") + .field("position", + &Frame::position, + &Frame::setPosition) + .field("tangent", + &Frame::tangent, + &Frame::setTangent) + .field("normal", + &Frame::normal, + &Frame::setNormal) + .field("binormal", + &Frame::normal, + &Frame::setNormal) + ; + class_("FrameSeq") + .constructor<>() + .constructor() + .function("size", &FrameSeq::size) + .function("at", &FrameSeq::at) + .function("toString", &FrameSeq::toString) + ; + + // Utility Classes + value_object("Domain") + .field("min", + &Domain::min, + &Domain::setMin) + .field("max", + &Domain::max, + &Domain::setMax) + ; + + value_object("DeBoorNet") + .field("knot", + &DeBoorNet::knot, + &DeBoorNet::setKnot) + .field("index", + &DeBoorNet::index, + &DeBoorNet::setIndex) + .field("multiplicity", + &DeBoorNet::multiplicity, + &DeBoorNet::setMultiplicity) + .field("numInsertions", + &DeBoorNet::numInsertions, + &DeBoorNet::setNumInsertions) + .field("dimension", + &DeBoorNet::dimension, + &DeBoorNet::setDimension) + .field("points", + &DeBoorNet::points, + &DeBoorNet::setPoints) + .field("result", + &DeBoorNet::result, + &DeBoorNet::setResult) + ; + + class_("BSpline") + .constructor<>() + //.constructor() + .constructor() + .constructor() + .constructor() + .constructor() + + .class_function("interpolateCubicNatural", + &BSpline::interpolateCubicNatural) + .class_function("interpolateCatmullRom", + &BSpline::interpolateCatmullRom, + allow_raw_pointers()) + .class_function("parseJson", &BSpline::parseJson) + + .property("degree", &BSpline::degree) + .property("order", &BSpline::order) + .property("dimension", &BSpline::dimension) + .property("controlPoints", &BSpline::controlPoints, + &BSpline::setControlPoints) + .property("knots", &BSpline::knots, &BSpline::setKnots) + .property("domain", &BSpline::domain) + + /* Property by index */ + .function("knotAt", &BSpline::knotAt) + .function("setKnotAt", &BSpline::setKnotAt) + + /* Query */ + .function("numControlPoints", &BSpline::numControlPoints) + .function("eval", &BSpline::eval) + .function("evalAll", &BSpline::evalAll) + .function("sample", + select_overload + (&BSpline::sample0)) + .function("sample", + select_overload + (&BSpline::sample1)) + .function("sample", &BSpline::sample) + .function("bisect", &BSpline::bisect) + .function("isClosed", &BSpline::isClosed) + + /* Serialization */ + .function("toJson", &BSpline::toJson) + + /* Transformations */ + .function("insertKnot", &BSpline::insertKnot) + .function("split", &BSpline::split) + .function("tension", &BSpline::tension) + .function("toBeziers", &BSpline::toBeziers) + .function("derive", + select_overload + (&BSpline::derive0)) + .function("derive", + select_overload + (&BSpline::derive1)) + .function("derive", + select_overload + (&BSpline::derive2)) + + /* Debug */ + .function("toString", &BSpline::toString) + ; + + enum_("BSplineType") + .value("Opened", BSpline::Type::Opened) + .value("Clamped", BSpline::Type::Clamped) + .value("Beziers", BSpline::Type::Beziers) + ; +} +#endif