├── .clang-format ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── cmake └── modules │ └── FindGLM.cmake ├── docs ├── README.md ├── curve-knot-insert.png └── split-surface.png ├── include └── tinynurbs │ ├── core │ ├── basis.h │ ├── check.h │ ├── curve.h │ ├── evaluate.h │ ├── modify.h │ └── surface.h │ ├── io │ └── obj.h │ ├── tinynurbs.h │ └── util │ ├── array2.h │ └── util.h └── tests ├── CMakeLists.txt ├── catch.hpp ├── test_curve.cpp ├── test_main.cpp ├── test_rational_curve.cpp ├── test_rational_surface.cpp └── test_surface.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | BreakBeforeBraces: Allman 3 | Standard: Cpp11 4 | IndentWidth: 4 5 | TabWidth: 4 6 | UseTab: Never 7 | ColumnLimit: 100 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # Other 35 | build/* 36 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dependencies/glm"] 2 | path = dependencies/glm 3 | url = https://github.com/g-truc/glm.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: require 3 | language: cpp 4 | 5 | matrix: 6 | include: 7 | - os: linux 8 | addons: 9 | apt: 10 | sources: 11 | - sourceline: "ppa:ubuntu-toolchain-r/test" 12 | packages: 13 | - g++-6 14 | - cmake 15 | env: COMPILER=g++-6 16 | 17 | before_script: 18 | - wget https://github.com/g-truc/glm/releases/download/0.9.9.0/glm-0.9.9.0.zip -O /tmp/glm.zip 19 | - unzip /tmp/glm.zip -d /tmp 20 | 21 | script: 22 | - mkdir build 23 | - cd build 24 | - cmake -DCMAKE_CXX_COMPILER=$COMPILER -DTINYNURBS_USE_OWN_GLM=OFF -DGLM_ROOT_DIR=/tmp/glm .. 25 | - make 26 | - make test 27 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(tinynurbs LANGUAGES CXX) 3 | 4 | set(CMAKE_CXX_STANDARD 14) 5 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/") 6 | 7 | set(BUILD_TESTS ON CACHE BOOL "Build unit tests") 8 | set(GLM_ROOT_DIR "" CACHE STRING "Root directory of GLM (>=0.9.9)") 9 | set(TINYNURBS_USE_OWN_GLM ON CACHE BOOL "Use own GLM library from submodule") 10 | message(STATUS "Variable from cache: ${GLM_ROOT_DIR}") 11 | 12 | if(NOT TINYNURBS_USE_OWN_GLM) 13 | find_package(GLM 0.9.9 REQUIRED MODULE) 14 | endif() 15 | 16 | set(HEADER_FILES 17 | include/tinynurbs/tinynurbs.h 18 | include/tinynurbs/core/basis.h 19 | include/tinynurbs/core/check.h 20 | include/tinynurbs/core/curve.h 21 | include/tinynurbs/core/evaluate.h 22 | include/tinynurbs/core/modify.h 23 | include/tinynurbs/core/surface.h 24 | include/tinynurbs/io/obj.h 25 | include/tinynurbs/util/util.h 26 | include/tinynurbs/util/array2.h 27 | ) 28 | source_group("Header Files" FILES ${HEADER_FILES}) 29 | source_group("CMake Files" FILES CMakeLists.txt) 30 | 31 | add_library(tinynurbs INTERFACE) 32 | add_library(tinynurbs::tinynurbs ALIAS tinynurbs ) 33 | if(NOT TINYNURBS_USE_OWN_GLM) 34 | target_link_libraries(tinynurbs INTERFACE glm) 35 | else() 36 | set(GLM_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/dependencies/glm) 37 | target_include_directories(tinynurbs INTERFACE $) 38 | endif() 39 | target_include_directories(tinynurbs INTERFACE $) 40 | 41 | add_custom_target(tinynurbs_dummy SOURCES ${HEADER_FILES} CMakeLists.txt) 42 | 43 | if(BUILD_TESTS) 44 | enable_testing() 45 | add_subdirectory(tests) 46 | endif() 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Pradeep Kumar Jayaraman 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /cmake/modules/FindGLM.cmake: -------------------------------------------------------------------------------- 1 | # Taken from https://github.com/Groovounet/glm-deprecated/tree/e1afbc9ceaacbc9aed7bb896d26c0872f8a2bf29/util 2 | # FindGLM - attempts to locate the glm matrix/vector library. 3 | # 4 | # This module defines the following variables (on success): 5 | # GLM_INCLUDE_DIRS - where to find glm/glm.hpp 6 | # GLM_FOUND - if the library was successfully located 7 | # 8 | # It is trying a few standard installation locations, but can be customized 9 | # with the following variables: 10 | # GLM_ROOT_DIR - root directory of a glm installation 11 | # Headers are expected to be found in either: 12 | # /glm/glm.hpp OR 13 | # /include/glm/glm.hpp 14 | # This variable can either be a cmake or environment 15 | # variable. Note however that changing the value 16 | # of the environment varible will NOT result in 17 | # re-running the header search and therefore NOT 18 | # adjust the variables set by this module. 19 | 20 | #============================================================================= 21 | # Copyright 2012 Carsten Neumann 22 | # 23 | # Distributed under the OSI-approved BSD License (the "License"); 24 | # see accompanying file Copyright.txt for details. 25 | # 26 | # This software is distributed WITHOUT ANY WARRANTY; without even the 27 | # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 28 | # See the License for more information. 29 | #============================================================================= 30 | # (To distribute this file outside of CMake, substitute the full 31 | # License text for the above reference.) 32 | 33 | if(TARGET glm) 34 | set(GLM_FOUND TRUE) 35 | return() 36 | endif() 37 | 38 | # default search dirs 39 | SET(_glm_HEADER_SEARCH_DIRS 40 | "/usr/include" 41 | "/usr/local/include") 42 | 43 | # check environment variable 44 | SET(_glm_ENV_ROOT_DIR "$ENV{GLM_ROOT_DIR}") 45 | 46 | IF(NOT GLM_ROOT_DIR AND _glm_ENV_ROOT_DIR) 47 | SET(GLM_ROOT_DIR "${_glm_ENV_ROOT_DIR}") 48 | ENDIF(NOT GLM_ROOT_DIR AND _glm_ENV_ROOT_DIR) 49 | 50 | # put user specified location at beginning of search 51 | IF(GLM_ROOT_DIR) 52 | SET(_glm_HEADER_SEARCH_DIRS "${GLM_ROOT_DIR}" 53 | "${GLM_ROOT_DIR}/include" 54 | ${_glm_HEADER_SEARCH_DIRS}) 55 | message("test1: ${GLM_ROOT_DIR}/include") 56 | ENDIF(GLM_ROOT_DIR) 57 | 58 | # locate header 59 | FIND_PATH(GLM_INCLUDE_DIR "glm/glm.hpp" 60 | PATHS ${_glm_HEADER_SEARCH_DIRS}) 61 | 62 | INCLUDE(FindPackageHandleStandardArgs) 63 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(GLM DEFAULT_MSG 64 | GLM_INCLUDE_DIR) 65 | 66 | IF(GLM_FOUND) 67 | SET(GLM_INCLUDE_DIRS "${GLM_INCLUDE_DIR}") 68 | MESSAGE(STATUS "GLM_INCLUDE_DIR = ${GLM_INCLUDE_DIR}") 69 | 70 | add_library(glm INTERFACE) 71 | target_include_directories(glm INTERFACE ${GLM_INCLUDE_DIR}) 72 | ENDIF(GLM_FOUND) -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # tinynurbs 2 | 3 | [![Build Status](https://travis-ci.com/pradeep-pyro/tinynurbs.svg?branch=master)](https://travis-ci.com/pradeep-pyro/tinynurbs) 4 | 5 | This is a lightweight header-only C++14 library for Non-Uniform Rational B-Spline curves and surfaces. The API is simple to use and the code is readable while being efficient. 6 | 7 | Some of the main features include: 8 | 9 | - Supports non-rational and rational curves and surfaces of any order 10 | - Evaluate point and derivatives of any order 11 | - Knot insertion, splitting without affecting the original shape 12 | - Wavefront OBJ format I/O 13 | 14 | The library is under development. 15 | 16 | ## Dependencies 17 | 18 | - [glm] (version 0.9.9 where PR #584 is merged is required since tinynurbs uses the `glm::vec` type) 19 | - C++14 compliant compiler 20 | 21 | ## Usage 22 | 23 | The entire API consists of free functions named `curve*` and `surface*` which accept a `Curve` / `RationalCurve` and `Surface` / `RationalSurface` object, respectively. 24 | Some example usage is given below. 25 | 26 | Create a non-rational planar curve: 27 | ```cpp 28 | tinynurbs::Curve crv; // Planar curve using float32 29 | crv.control_points = {glm::vec3(-1, 0, 0), // std::vector of 3D points 30 | glm::vec3(0, 1, 0), 31 | glm::vec3(1, 0, 0) 32 | }; 33 | crv.knots = {0, 0, 0, 1, 1, 1}; // std::vector of floats 34 | crv.degree = 2; 35 | ``` 36 | 37 | Check if created curve is valid: 38 | ```cpp 39 | if (!tinynurbs::curveIsValid(crv)) { 40 | // check if degree, knots and control points are configured as per 41 | // #knots == #control points + degree + 1 42 | } 43 | ``` 44 | 45 | Evaluate point and tangent on curve: 46 | ```cpp 47 | glm::vec3 pt = tinynurbs::curvePoint(crv, 0.f); 48 | // Outputs a point [-1, 0] 49 | glm::vec3 tgt = tinynurbs::curveTangent(crv, 0.5f); 50 | // Outputs a vector [1, 0] 51 | ``` 52 | 53 | Insert a single knot at u=0.25 and double knot at u=0.75: 54 | ```cpp 55 | crv = tinynurbs::curveKnotInsert(crv, 0.25); 56 | crv = tinynurbs::curveKnotInsert(crv, 0.75, 2); 57 | ``` 58 | Left: original curve, Right: after knot insertion 59 | 60 | ![curve knotinsert](curve-knot-insert.png) 61 | 62 | Write the curve to an OBJ file: 63 | ```cpp 64 | tinynurbs::curveSaveOBJ("output_curve.obj", crv); 65 | ``` 66 | creates a file with the following contents: 67 | ``` 68 | v -1 0 0 1 69 | v -0.75 0.5 0 1 70 | v 0 1.25 0 1 71 | v 0.5 0.75 0 1 72 | v 0.75 0.5 0 1 73 | v 1 0 0 1 74 | cstype bspline 75 | deg 2 76 | curv 0 1 1 2 3 4 5 6 77 | parm u 0 0 0 0.25 0.75 0.75 1 1 1 78 | end 79 | ``` 80 | 81 | Create a rational surface shaped like a hemisphere: 82 | 83 | ```cpp 84 | tinynurbs::RationalSurface srf; 85 | srf.degree_u = 3; 86 | srf.degree_v = 3; 87 | srf.knots_u = {0, 0, 0, 0, 1, 1, 1, 1}; 88 | srf.knots_v = {0, 0, 0, 0, 1, 1, 1, 1}; 89 | 90 | // 2D array of control points using tinynurbs::array2 container 91 | // Example from geometrictools.com/Documentation/NURBSCircleSphere.pdf 92 | srf.control_points = {4, 4, 93 | {glm::vec3(0, 0, 1), glm::vec3(0, 0, 1), glm::vec3(0, 0, 1), glm::vec3(0, 0, 1), 94 | glm::vec3(2, 0, 1), glm::vec3(2, 4, 1), glm::vec3(-2, 4, 1), glm::vec3(-2, 0, 1), 95 | glm::vec3(2, 0, -1), glm::vec3(2, 4, -1), glm::vec3(-2, 4, -1), glm::vec3(-2, 0, -1), 96 | glm::vec3(0, 0, -1), glm::vec3(0, 0, -1), glm::vec3(0, 0, -1), glm::vec3(0, 0, -1) 97 | } 98 | }; 99 | srf.weights = {4, 4, 100 | {1, 1.f/3.f, 1.f/3.f, 1, 101 | 1.f/3.f, 1.f/9.f, 1.f/9.f, 1.f/3.f, 102 | 1.f/3.f, 1.f/9.f, 1.f/9.f, 1.f/3.f, 103 | 1, 1.f/3.f, 1.f/3.f, 1 104 | } 105 | }; 106 | ``` 107 | 108 | Split the surface into two along v=0.25: 109 | ```cpp 110 | tinynurbs::RationalSurface left, right; 111 | std::tie(left, right) = tinynurbs::surfaceSplitV(srf, 0.25); 112 | ``` 113 | Left: original surface, Right: after splitting 114 | 115 | ![split surface](split-surface.png) 116 | 117 | Write the surface to an OBJ file: 118 | ```cpp 119 | tinynurbs::surfaceSaveOBJ("output_surface.obj", srf); 120 | ``` 121 | creates a file with the following contents: 122 | ``` 123 | v 0 0 1 1 124 | v 2 0 1 0.333333 125 | v 2 0 -1 0.333333 126 | v 0 0 -1 1 127 | v 0 0 1 0.333333 128 | v 2 4 1 0.111111 129 | v 2 4 -1 0.111111 130 | v 0 0 -1 0.333333 131 | v 0 0 1 0.333333 132 | v -2 4 1 0.111111 133 | v -2 4 -1 0.111111 134 | v 0 0 -1 0.333333 135 | v 0 0 1 1 136 | v -2 0 1 0.333333 137 | v -2 0 -1 0.333333 138 | v 0 0 -1 1 139 | cstype rat bspline 140 | deg 3 3 141 | surf 0 1 0 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 142 | parm u 0 0 0 0 1 1 1 1 143 | parm v 0 0 0 0 1 1 1 1 144 | end 145 | ``` 146 | 147 | ## Primary Reference 148 | 149 | - "The NURBS Book," Les Piegl and Wayne Tiller, Springer-Verlag, 1995. 150 | 151 | [glm]: https://github.com/g-truc/glm 152 | -------------------------------------------------------------------------------- /docs/curve-knot-insert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pradeep-pyro/tinynurbs/2669a6b4b6c3d60128f845f599b83e31677045d6/docs/curve-knot-insert.png -------------------------------------------------------------------------------- /docs/split-surface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pradeep-pyro/tinynurbs/2669a6b4b6c3d60128f845f599b83e31677045d6/docs/split-surface.png -------------------------------------------------------------------------------- /include/tinynurbs/core/basis.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Low-level functions for evaluating B-spline basis functions and their derivatives 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be found in 5 | * the LICENSE file. 6 | */ 7 | 8 | #ifndef TINYNURBS_BASIS_H 9 | #define TINYNURBS_BASIS_H 10 | 11 | #include "../util/array2.h" 12 | #include "../util/util.h" 13 | #include 14 | 15 | namespace tinynurbs 16 | { 17 | 18 | /** 19 | * Find the span of the given parameter in the knot vector. 20 | * @param[in] degree Degree of the curve. 21 | * @param[in] knots Knot vector of the curve. 22 | * @param[in] u Parameter value. 23 | * @return Span index into the knot vector such that (span - 1) < u <= span 24 | */ 25 | template int findSpan(unsigned int degree, const std::vector &knots, T u) 26 | { 27 | // index of last control point 28 | int n = static_cast(knots.size()) - degree - 2; 29 | assert(n >= 0); 30 | 31 | // For values of u that lies outside the domain 32 | if (u >= knots[n + 1]) 33 | { 34 | return n; 35 | } 36 | if (u <= knots[degree]) 37 | { 38 | return degree; 39 | } 40 | 41 | // Binary search 42 | // TODO: Replace this with std::lower_bound 43 | int low = degree; 44 | int high = n + 1; 45 | int mid = (int)std::floor((low + high) / 2.0); 46 | while (u < knots[mid] || u >= knots[mid + 1]) 47 | { 48 | if (u < knots[mid]) 49 | { 50 | high = mid; 51 | } 52 | else 53 | { 54 | low = mid; 55 | } 56 | mid = (int)std::floor((low + high) / 2.0); 57 | } 58 | return mid; 59 | } 60 | 61 | /** 62 | * Compute a single B-spline basis function 63 | * @param[in] i The ith basis function to compute. 64 | * @param[in] deg Degree of the basis function. 65 | * @param[in] knots Knot vector corresponding to the basis functions. 66 | * @param[in] u Parameter to evaluate the basis functions at. 67 | * @return The value of the ith basis function at u. 68 | */ 69 | template T bsplineOneBasis(int i, unsigned int deg, const std::vector &U, T u) 70 | { 71 | int m = static_cast(U.size()) - 1; 72 | // Special case 73 | if ((i == 0 && close(u, U[0])) || (i == m - deg - 1 && close(u, U[m]))) 74 | { 75 | return 1.0; 76 | } 77 | // Local property ensures that basis function is zero outside span 78 | if (u < U[i] || u >= U[i + deg + 1]) 79 | { 80 | return 0.0; 81 | } 82 | // Initialize zeroth-degree functions 83 | std::vector N; 84 | N.resize(deg + 1); 85 | for (int j = 0; j <= static_cast(deg); j++) 86 | { 87 | N[j] = (u >= U[i + j] && u < U[i + j + 1]) ? 1.0 : 0.0; 88 | } 89 | // Compute triangular table 90 | for (int k = 1; k <= static_cast(deg); k++) 91 | { 92 | T saved = (util::close(N[0], 0.0)) ? 0.0 : ((u - U[i]) * N[0]) / (U[i + k] - U[i]); 93 | for (int j = 0; j < static_cast(deg) - k + 1; j++) 94 | { 95 | T Uleft = U[i + j + 1]; 96 | T Uright = U[i + j + k + 1]; 97 | if (util::close(N[j + 1], 0.0)) 98 | { 99 | N[j] = saved; 100 | saved = 0.0; 101 | } 102 | else 103 | { 104 | T temp = N[j + 1] / (Uright - Uleft); 105 | N[j] = saved + (Uright - u) * temp; 106 | saved = (u - Uleft) * temp; 107 | } 108 | } 109 | } 110 | return N[0]; 111 | } 112 | 113 | /** 114 | * Compute all non-zero B-spline basis functions 115 | * @param[in] deg Degree of the basis function. 116 | * @param[in] span Index obtained from findSpan() corresponding the u and knots. 117 | * @param[in] knots Knot vector corresponding to the basis functions. 118 | * @param[in] u Parameter to evaluate the basis functions at. 119 | * @return N Values of (deg+1) non-zero basis functions. 120 | */ 121 | template 122 | std::vector bsplineBasis(unsigned int deg, int span, const std::vector &knots, T u) 123 | { 124 | std::vector N; 125 | N.resize(deg + 1, T(0)); 126 | std::vector left, right; 127 | left.resize(deg + 1, static_cast(0.0)); 128 | right.resize(deg + 1, static_cast(0.0)); 129 | T saved = 0.0, temp = 0.0; 130 | 131 | N[0] = 1.0; 132 | 133 | for (int j = 1; j <= static_cast(deg); j++) 134 | { 135 | left[j] = (u - knots[span + 1 - j]); 136 | right[j] = knots[span + j] - u; 137 | saved = 0.0; 138 | for (int r = 0; r < j; r++) 139 | { 140 | temp = N[r] / (right[r + 1] + left[j - r]); 141 | N[r] = saved + right[r + 1] * temp; 142 | saved = left[j - r] * temp; 143 | } 144 | N[j] = saved; 145 | } 146 | return N; 147 | } 148 | 149 | /** 150 | * Compute all non-zero derivatives of B-spline basis functions 151 | * @param[in] deg Degree of the basis function. 152 | * @param[in] span Index obtained from findSpan() corresponding the u and knots. 153 | * @param[in] knots Knot vector corresponding to the basis functions. 154 | * @param[in] u Parameter to evaluate the basis functions at. 155 | * @param[in] num_ders Number of derivatives to compute (num_ders <= deg) 156 | * @return ders Values of non-zero derivatives of basis functions. 157 | */ 158 | template 159 | array2 bsplineDerBasis(unsigned int deg, int span, const std::vector &knots, T u, 160 | int num_ders) 161 | { 162 | std::vector left, right; 163 | left.resize(deg + 1, 0.0); 164 | right.resize(deg + 1, 0.0); 165 | T saved = 0.0, temp = 0.0; 166 | 167 | array2 ndu(deg + 1, deg + 1); 168 | ndu(0, 0) = 1.0; 169 | 170 | for (int j = 1; j <= static_cast(deg); j++) 171 | { 172 | left[j] = u - knots[span + 1 - j]; 173 | right[j] = knots[span + j] - u; 174 | saved = 0.0; 175 | 176 | for (int r = 0; r < j; r++) 177 | { 178 | // Lower triangle 179 | ndu(j, r) = right[r + 1] + left[j - r]; 180 | temp = ndu(r, j - 1) / ndu(j, r); 181 | // Upper triangle 182 | ndu(r, j) = saved + right[r + 1] * temp; 183 | saved = left[j - r] * temp; 184 | } 185 | 186 | ndu(j, j) = saved; 187 | } 188 | 189 | array2 ders(num_ders + 1, deg + 1, T(0)); 190 | 191 | for (int j = 0; j <= static_cast(deg); j++) 192 | { 193 | ders(0, j) = ndu(j, deg); 194 | } 195 | 196 | array2 a(2, deg + 1); 197 | 198 | for (int r = 0; r <= static_cast(deg); r++) 199 | { 200 | int s1 = 0; 201 | int s2 = 1; 202 | a(0, 0) = 1.0; 203 | 204 | for (int k = 1; k <= num_ders; k++) 205 | { 206 | T d = 0.0; 207 | int rk = r - k; 208 | int pk = deg - k; 209 | int j1 = 0; 210 | int j2 = 0; 211 | 212 | if (r >= k) 213 | { 214 | a(s2, 0) = a(s1, 0) / ndu(pk + 1, rk); 215 | d = a(s2, 0) * ndu(rk, pk); 216 | } 217 | 218 | if (rk >= -1) 219 | { 220 | j1 = 1; 221 | } 222 | else 223 | { 224 | j1 = -rk; 225 | } 226 | 227 | if (r - 1 <= pk) 228 | { 229 | j2 = k - 1; 230 | } 231 | else 232 | { 233 | j2 = deg - r; 234 | } 235 | 236 | for (int j = j1; j <= j2; j++) 237 | { 238 | a(s2, j) = (a(s1, j) - a(s1, j - 1)) / ndu(pk + 1, rk + j); 239 | d += a(s2, j) * ndu(rk + j, pk); 240 | } 241 | 242 | if (r <= pk) 243 | { 244 | a(s2, k) = -a(s1, k - 1) / ndu(pk + 1, r); 245 | d += a(s2, k) * ndu(r, pk); 246 | } 247 | 248 | ders(k, r) = d; 249 | 250 | int temp = s1; 251 | s1 = s2; 252 | s2 = temp; 253 | } 254 | } 255 | 256 | T fac = static_cast(deg); 257 | for (int k = 1; k <= num_ders; k++) 258 | { 259 | for (int j = 0; j <= static_cast(deg); j++) 260 | { 261 | ders(k, j) *= fac; 262 | } 263 | fac *= static_cast(deg - k); 264 | } 265 | 266 | return ders; 267 | } 268 | 269 | } // namespace tinynurbs 270 | 271 | #endif // TINYNURBS_BASIS_H 272 | -------------------------------------------------------------------------------- /include/tinynurbs/core/check.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Functionality for checking validity and properties of NURBS curves and 3 | * surfaces 4 | * 5 | * Use of this source code is governed by a BSD-style license that can be found in 6 | * the LICENSE file. 7 | */ 8 | 9 | #ifndef TINYNURBS_CHECK_H 10 | #define TINYNURBS_CHECK_H 11 | 12 | #include "curve.h" 13 | #include "glm/glm.hpp" 14 | #include "surface.h" 15 | #include 16 | #include 17 | #include 18 | 19 | namespace tinynurbs 20 | { 21 | 22 | ///////////////////////////////////////////////////////////////////// 23 | 24 | namespace internal 25 | { 26 | 27 | /** 28 | * Checks if the relation between degree, number of knots, and 29 | * number of control points is valid 30 | * @param[in] degree Degree 31 | * @param[in] num_knots Number of knot values 32 | * @param[in] num_ctrl_pts Number of control points 33 | * @return Whether the relationship is valid 34 | */ 35 | inline bool isValidRelation(unsigned int degree, size_t num_knots, size_t num_ctrl_pts) 36 | { 37 | return (num_knots - degree - 1) == num_ctrl_pts; 38 | } 39 | 40 | /** 41 | * isKnotVectorMonotonic returns whether the knots are in ascending order 42 | * @tparam Type of knot values 43 | * @param[in] knots Knot vector 44 | * @return Whether monotonic 45 | */ 46 | template bool isKnotVectorMonotonic(const std::vector &knots) 47 | { 48 | return std::is_sorted(knots.begin(), knots.end()); 49 | } 50 | 51 | /** 52 | * Returns whether the curve is valid 53 | * @tparam T Type of control point coordinates, knot values 54 | * @param[in] degree Degree of curve 55 | * @param[in] knots Knot vector of curve 56 | * @param[in] control_points Control points of curve 57 | * @return Whether valid 58 | */ 59 | template 60 | bool curveIsValid(unsigned int degree, const std::vector &knots, 61 | const std::vector> &control_points) 62 | { 63 | if (degree < 1 || degree > 9) 64 | { 65 | return false; 66 | } 67 | if (!isValidRelation(degree, knots.size(), control_points.size())) 68 | { 69 | return false; 70 | } 71 | if (!isKnotVectorMonotonic(knots)) 72 | { 73 | return false; 74 | } 75 | return true; 76 | } 77 | 78 | /** 79 | * Returns whether the curve is valid 80 | * @tparam T Type of control point coordinates, knot values and weights 81 | * @param[in] degree Degree of curve 82 | * @param[in] knots Knot vector of curve 83 | * @param[in] control_points Control points of curve 84 | * @return Whether valid 85 | */ 86 | template 87 | bool curveIsValid(unsigned int degree, const std::vector &knots, 88 | const std::vector> &control_points, const std::vector &weights) 89 | { 90 | if (!isValidRelation(degree, knots.size(), control_points.size())) 91 | { 92 | return false; 93 | } 94 | if (weights.size() != control_points.size()) 95 | { 96 | return false; 97 | } 98 | return true; 99 | } 100 | 101 | /** 102 | * Returns whether the surface is valid 103 | * @tparam T Type of control point coordinates, knot values 104 | * @param[in] degree_u Degree of surface along u-direction 105 | * @param[in] degree_v Degree of surface along v-direction 106 | * @param[in] knots_u Knot vector of surface along u-direction 107 | * @param[in] knots_v Knot vector of surface along v-direction 108 | * @param[in] control_points Control points grid of surface 109 | * @return Whether valid 110 | */ 111 | template 112 | bool surfaceIsValid(unsigned int degree_u, unsigned int degree_v, const std::vector &knots_u, 113 | const std::vector &knots_v, const array2> &control_points) 114 | { 115 | if (degree_u < 1 || degree_u > 9 || degree_v < 1 || degree_v > 9) 116 | { 117 | return false; 118 | } 119 | if (!isValidRelation(degree_u, knots_u.size(), control_points.rows()) || 120 | !isValidRelation(degree_v, knots_v.size(), control_points.cols())) 121 | { 122 | return false; 123 | } 124 | if (!isKnotVectorMonotonic(knots_u) || !isKnotVectorMonotonic(knots_v)) 125 | { 126 | return false; 127 | } 128 | return true; 129 | } 130 | 131 | /** 132 | * Returns whether the rational surface is valid 133 | * @tparam T Type of control point coordinates, knot values 134 | * @param[in] degree_u Degree of surface along u-direction 135 | * @param[in] degree_v Degree of surface along v-direction 136 | * @param[in] knots_u Knot vector of surface along u-direction 137 | * @param[in] knots_v Knot vector of surface along v-direction 138 | * @param[in] control_points Control points grid of surface 139 | * @param[in] weights Weights corresponding to control point grid of surface 140 | * @return Whether valid 141 | */ 142 | template 143 | bool surfaceIsValid(unsigned int degree_u, unsigned int degree_v, const std::vector &knots_u, 144 | const std::vector &knots_v, const array2> &control_points, 145 | const array2 &weights) 146 | { 147 | if (!surfaceIsValid(degree_u, degree_v, knots_u, knots_v, control_points)) 148 | { 149 | return false; 150 | } 151 | if (control_points.rows() != weights.rows() || control_points.cols() != weights.cols()) 152 | { 153 | return false; 154 | } 155 | return true; 156 | } 157 | 158 | /** 159 | * Returns whether the given knot vector is closed by checking the 160 | * periodicity of knot vectors near the start and end 161 | * @param[in] degree Degree of curve/surface 162 | * @param[in] knots Knot vector of curve/surface 163 | * @return Whether knot vector is closed 164 | */ 165 | template bool isKnotVectorClosed(unsigned int degree, const std::vector &knots) 166 | { 167 | for (int i = 0; i < degree - 1; ++i) 168 | { 169 | int j = knots.size() - degree + i; 170 | if (std::abs((knots[i + 1] - knots[i]) - (knots[j + 1] - knots[j])) > 171 | std::numeric_limits::epsilon()) 172 | { 173 | return false; 174 | } 175 | } 176 | return true; 177 | } 178 | 179 | /** 180 | * Returns whether the given knot vector is closed by checking the 181 | * periodicity of knot vectors near the start and end 182 | * @param[in] degree Degree of curve/surface 183 | * @param[in] vec Array of any control points/weights 184 | * @return Whether knot vector is closed 185 | */ 186 | template bool isArray1Closed(unsigned int degree, const std::vector &vec) 187 | { 188 | for (int i = 0; i < degree; ++i) 189 | { 190 | int j = vec.size() - degree + i; 191 | if (glm::length(vec[i] - vec[j]) > 1e-5) 192 | { 193 | return false; 194 | } 195 | } 196 | return true; 197 | } 198 | 199 | /** 200 | * Returns whether the 2D array is closed along the u-direction 201 | * i.e., along rows. 202 | * @param[in] degree_u Degree along u-direction 203 | * @param[in] arr 2D array of control points / weights 204 | * @return Whether closed along u-direction 205 | */ 206 | template bool isArray2ClosedU(unsigned int degree_u, const array2 &arr) 207 | { 208 | for (int i = 0; i < degree_u; ++i) 209 | { 210 | for (int j = 0; j < arr.cols(); ++j) 211 | { 212 | int k = arr.cols() - degree_u + i; 213 | if (glm::length(arr(i, j) - arr(k, j)) > 1e-5) 214 | { 215 | return false; 216 | } 217 | } 218 | } 219 | return true; 220 | } 221 | 222 | /** 223 | * Returns whether the 2D array is closed along the v-direction 224 | * i.e., along columns. 225 | * @param[in] degree_v Degree along v-direction 226 | * @param[in] arr 2D array of control points / weights 227 | * @return Whether closed along v-direction 228 | */ 229 | template bool isArray2ClosedV(unsigned int degree_v, const array2 &arr) 230 | { 231 | for (int i = 0; i < arr.rows(); ++i) 232 | { 233 | for (int j = 0; j < degree_v; j++) 234 | { 235 | int k = arr.rows() - degree_v + i; 236 | if (glm::length(arr(i, j) - arr(i, k)) > 1e-5) 237 | { 238 | return false; 239 | } 240 | } 241 | } 242 | return true; 243 | } 244 | 245 | } // namespace internal 246 | 247 | ///////////////////////////////////////////////////////////////////// 248 | 249 | /** 250 | * Returns the multiplicity of the knot at index 251 | * @tparam Type of knot values 252 | * @param[in] knots Knot vector 253 | * @param[in] knot_val Knot of interest 254 | * @return Multiplicity (>= 0) 255 | */ 256 | template unsigned int knotMultiplicity(const std::vector &knots, T knot_val) 257 | { 258 | T eps = std::numeric_limits::epsilon(); 259 | unsigned int mult = 0; 260 | for (const T knot : knots) 261 | { 262 | if (std::abs(knot_val - knot) < eps) 263 | { 264 | ++mult; 265 | } 266 | } 267 | return mult; 268 | } 269 | 270 | /** 271 | * Returns the mulitplicity of the knot at index 272 | * @tparam Type of knot values 273 | * @param[in] knots Knot vector 274 | * @param[in] index Index of knot of interest 275 | * @return Multiplicity (>= 1) 276 | */ 277 | template 278 | [[deprecated("Use knotMultiplicity(knots, param).")]] 279 | unsigned int knotMultiplicity(const std::vector &knots, unsigned int index) 280 | { 281 | T curr_knot_val = knots[index]; 282 | T eps = std::numeric_limits::epsilon(); 283 | unsigned int mult = 0; 284 | for (const T knot : knots) 285 | { 286 | if (std::abs(curr_knot_val - knot) < eps) 287 | { 288 | ++mult; 289 | } 290 | } 291 | return mult; 292 | } 293 | 294 | /** 295 | * Returns whether the curve is valid 296 | * @tparam T Type of control point coordinates, knot values 297 | * @param[in] crv Curve object 298 | * @return Whether valid 299 | */ 300 | template bool curveIsValid(const Curve &crv) 301 | { 302 | return internal::curveIsValid(crv.degree, crv.knots, crv.control_points); 303 | } 304 | 305 | /** 306 | * Returns whether the curve is valid 307 | * @tparam T Type of control point coordinates, knot values 308 | * @param[in] crv RationalCurve object 309 | * @return Whether valid 310 | */ 311 | template bool curveIsValid(const RationalCurve &crv) 312 | { 313 | return internal::curveIsValid(crv.degree, crv.knots, crv.control_points, crv.weights); 314 | } 315 | 316 | /** 317 | * Returns whether the surface is valid 318 | * @tparam T Type of control point coordinates, knot values 319 | * @param srf Surface object 320 | * @return Whether valid 321 | */ 322 | template bool surfaceIsValid(const Surface &srf) 323 | { 324 | return internal::surfaceIsValid(srf.degree_u, srf.degree_v, srf.knots_u, srf.knots_v, 325 | srf.control_points); 326 | } 327 | 328 | /** 329 | * Returns whether the rational surface is valid 330 | * @tparam T Type of control point coordinates, knot values 331 | * @param[in] srf RationalSurface object 332 | * @return Whether valid 333 | */ 334 | template bool surfaceIsValid(const RationalSurface &srf) 335 | { 336 | return internal::surfaceIsValid(srf.degree_u, srf.degree_v, srf.knots_u, srf.knots_v, 337 | srf.control_points, srf.weights); 338 | } 339 | 340 | /** 341 | * Checks whether the curve is closed 342 | * @param[in] crv Curve object 343 | * @return Whether closed 344 | */ 345 | template bool curveIsClosed(const Curve &crv) 346 | { 347 | return internal::isArray1Closed(crv.degree, crv.control_points) && 348 | internal::isKnotVectorClosed(crv.degree, crv.knots); 349 | } 350 | 351 | /** 352 | * Checks whether the rational curve is closed 353 | * @param[in] crv RationalCurve object 354 | * @return Whether closed 355 | */ 356 | template bool curveIsClosed(const RationalCurve &crv) 357 | { 358 | return internal::isArray1Closed(crv.degree, crv.control_points) && 359 | internal::isArray1Closed(crv.degree, crv.weights) && 360 | internal::isKnotVectorClosed(crv.degree, crv.knots); 361 | } 362 | 363 | /** 364 | * Checks whether the surface is closed along u-direction 365 | * @param[in] srf Surface object 366 | * @return Whether closed along u-direction 367 | */ 368 | template bool surfaceIsClosedU(const Surface &srf) 369 | { 370 | return internal::isArray2ClosedU(srf.degree_u, srf.control_points) && 371 | internal::isKnotVectorClosed(srf.degree_u, srf.knots_u); 372 | } 373 | 374 | /** 375 | * Checks whether the surface is closed along v-direction 376 | * @param[in] srf Surface object 377 | * @return Whether closed along v-direction 378 | */ 379 | template bool surfaceIsClosedV(const Surface &srf) 380 | { 381 | return internal::isArray2ClosedV(srf.degree_v, srf.control_points) && 382 | internal::isKnotVectorClosed(srf.degree_v, srf.knots_v); 383 | } 384 | 385 | /** 386 | * Checks whether the rational surface is closed along u-direction 387 | * @param[in] srf RationalSurface object 388 | * @return Whether closed along u-direction 389 | */ 390 | template bool surfaceIsClosedU(const RationalSurface &srf) 391 | { 392 | return internal::isArray2ClosedU(srf.degree_u, srf.control_points) && 393 | internal::isKnotVectorClosed(srf.degree_u, srf.knots_u) && 394 | internal::isArray2ClosedU(srf.degree_u, srf.weights); 395 | } 396 | 397 | /** 398 | * Checks whether the rational surface is closed along v-direction 399 | * @param[in] srf RationalSurface object 400 | * @return Whether closed along v-direction 401 | */ 402 | template bool surfaceIsClosedV(const RationalSurface &srf) 403 | { 404 | return internal::isArray2ClosedV(srf.degree_v, srf.control_points) && 405 | internal::isKnotVectorClosed(srf.degree_v, srf.knots_v) && 406 | internal::isArray2ClosedV(srf.degree_v, srf.weights); 407 | } 408 | 409 | } // namespace tinynurbs 410 | 411 | #endif // TINYNURBS_CHECK_H 412 | -------------------------------------------------------------------------------- /include/tinynurbs/core/curve.h: -------------------------------------------------------------------------------- 1 | /** 2 | * The Curve class represents a non-uniform polynomial B-spline curve, while the 3 | * RationalCurve class represents a non-uniform rational B-spline (NURBS) curve. 4 | * 5 | * Use of this source code is governed by a BSD-style license that can be found in 6 | * the LICENSE file. 7 | */ 8 | 9 | #ifndef TINYNURBS_CURVE_H 10 | #define TINYNURBS_CURVE_H 11 | 12 | #include "glm/glm.hpp" 13 | #include 14 | #include 15 | #include 16 | 17 | namespace tinynurbs 18 | { 19 | 20 | // Forward declaration 21 | template struct RationalCurve; 22 | 23 | /** 24 | Struct for holding a polynomial B-spline curve 25 | @tparam T Data type of control points and knots (float or double) 26 | */ 27 | template struct Curve 28 | { 29 | unsigned int degree; 30 | std::vector knots; 31 | std::vector> control_points; 32 | 33 | Curve() = default; 34 | Curve(const RationalCurve &crv) : Curve(crv.degree, crv.knots, crv.control_points) {} 35 | Curve(unsigned int degree, const std::vector &knots, 36 | const std::vector> &control_points) 37 | : degree(degree), knots(knots), control_points(control_points) 38 | { 39 | } 40 | }; 41 | 42 | /** 43 | Struct for holding a rational B-spline curve 44 | @tparam T Data type of control points and knots (float or double) 45 | */ 46 | template struct RationalCurve 47 | { 48 | unsigned int degree; 49 | std::vector knots; 50 | std::vector> control_points; 51 | std::vector weights; 52 | 53 | RationalCurve() = default; 54 | RationalCurve(const Curve &crv) 55 | : RationalCurve(crv, std::vector(crv.control_points.size(), 1.0)) 56 | { 57 | } 58 | RationalCurve(const Curve &crv, const std::vector &weights) 59 | : RationalCurve(crv.degree, crv.knots, crv.control_points, weights) 60 | { 61 | } 62 | RationalCurve(unsigned int degree, const std::vector &knots, 63 | const std::vector> &control_points, const std::vector weights) 64 | : degree(degree), knots(knots), control_points(control_points), weights(weights) 65 | { 66 | } 67 | }; 68 | 69 | // Typedefs for ease of use 70 | typedef Curve Curve3f; 71 | typedef Curve Curve3d; 72 | typedef RationalCurve RationalCurve3f; 73 | typedef RationalCurve RationalCurve3d; 74 | 75 | } // namespace tinynurbs 76 | 77 | #endif // TINYNURBS_CURVE_H 78 | -------------------------------------------------------------------------------- /include/tinynurbs/core/evaluate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Core functionality for evaluating points, derivatives and related 3 | * quantities on NURBS curves and surfaces. 4 | * 5 | * Use of this source code is governed by a BSD-style license that can be found in 6 | * the LICENSE file. 7 | */ 8 | 9 | #ifndef TINYNURBS_EVALUATE_H 10 | #define TINYNURBS_EVALUATE_H 11 | 12 | #include "../util/util.h" 13 | #include "basis.h" 14 | #include "curve.h" 15 | #include "glm/glm.hpp" 16 | #include "surface.h" 17 | #include "tinynurbs/util/array2.h" 18 | #include 19 | #include 20 | 21 | namespace tinynurbs 22 | { 23 | 24 | ///////////////////////////////////////////////////////////////////// 25 | 26 | namespace internal 27 | { 28 | 29 | /** 30 | * Evaluate point on a nonrational NURBS curve 31 | * @param[in] degree Degree of the given curve. 32 | * @param[in] knots Knot vector of the curve. 33 | * @param[in] control_points Control points of the curve. 34 | * @param[in] u Parameter to evaluate the curve at. 35 | * @return point Resulting point on the curve at parameter u. 36 | */ 37 | template 38 | glm::vec curvePoint(unsigned int degree, const std::vector &knots, 39 | const std::vector> &control_points, T u) 40 | { 41 | // Initialize result to 0s 42 | glm::vec point(T(0)); 43 | 44 | // Find span and corresponding non-zero basis functions 45 | int span = findSpan(degree, knots, u); 46 | std::vector N = bsplineBasis(degree, span, knots, u); 47 | 48 | // Compute point 49 | for (unsigned int j = 0; j <= degree; j++) 50 | { 51 | point += static_cast(N[j]) * control_points[span - degree + j]; 52 | } 53 | return point; 54 | } 55 | 56 | /** 57 | * Evaluate derivatives of a non-rational NURBS curve 58 | * @param[in] degree Degree of the curve 59 | * @param[in] knots Knot vector of the curve. 60 | * @param[in] control_points Control points of the curve. 61 | * @param[in] num_ders Number of times to derivate. 62 | * @param[in] u Parameter to evaluate the derivatives at. 63 | * @return curve_ders Derivatives of the curve at u. 64 | * E.g. curve_ders[n] is the nth derivative at u, where 0 <= n <= num_ders. 65 | */ 66 | template 67 | std::vector> curveDerivatives(unsigned int degree, const std::vector &knots, 68 | const std::vector> &control_points, 69 | int num_ders, T u) 70 | { 71 | 72 | typedef glm::vec tvecn; 73 | using std::vector; 74 | 75 | std::vector> curve_ders; 76 | curve_ders.resize(num_ders + 1); 77 | 78 | // Assign higher order derivatives to zero 79 | for (int k = degree + 1; k <= num_ders; k++) 80 | { 81 | curve_ders[k] = tvecn(0.0); 82 | } 83 | 84 | // Find the span and corresponding non-zero basis functions & derivatives 85 | int span = findSpan(degree, knots, u); 86 | array2 ders = bsplineDerBasis(degree, span, knots, u, num_ders); 87 | 88 | // Compute first num_ders derivatives 89 | int du = num_ders < static_cast(degree) ? num_ders : static_cast(degree); 90 | for (int k = 0; k <= du; k++) 91 | { 92 | curve_ders[k] = tvecn(0.0); 93 | for (int j = 0; j <= static_cast(degree); j++) 94 | { 95 | curve_ders[k] += static_cast(ders(k, j)) * control_points[span - degree + j]; 96 | } 97 | } 98 | return curve_ders; 99 | } 100 | 101 | /** 102 | * Evaluate point on a nonrational NURBS surface 103 | * @param[in] degree_u Degree of the given surface in u-direction. 104 | * @param[in] degree_v Degree of the given surface in v-direction. 105 | * @param[in] knots_u Knot vector of the surface in u-direction. 106 | * @param[in] knots_v Knot vector of the surface in v-direction. 107 | * @param[in] control_points Control points of the surface in a 2d array. 108 | * @param[in] u Parameter to evaluate the surface at. 109 | * @param[in] v Parameter to evaluate the surface at. 110 | * @return point Resulting point on the surface at (u, v). 111 | */ 112 | template 113 | glm::vec surfacePoint(unsigned int degree_u, unsigned int degree_v, 114 | const std::vector &knots_u, const std::vector &knots_v, 115 | const array2> &control_points, T u, T v) 116 | { 117 | 118 | // Initialize result to 0s 119 | glm::vec point(T(0.0)); 120 | 121 | // Find span and non-zero basis functions 122 | int span_u = findSpan(degree_u, knots_u, u); 123 | int span_v = findSpan(degree_v, knots_v, v); 124 | std::vector Nu = bsplineBasis(degree_u, span_u, knots_u, u); 125 | std::vector Nv = bsplineBasis(degree_v, span_v, knots_v, v); 126 | 127 | for (int l = 0; l <= degree_v; l++) 128 | { 129 | glm::vec temp(0.0); 130 | for (int k = 0; k <= degree_u; k++) 131 | { 132 | temp += static_cast(Nu[k]) * 133 | control_points(span_u - degree_u + k, span_v - degree_v + l); 134 | } 135 | 136 | point += static_cast(Nv[l]) * temp; 137 | } 138 | return point; 139 | } 140 | 141 | /** 142 | * Evaluate derivatives on a non-rational NURBS surface 143 | * @param[in] degree_u Degree of the given surface in u-direction. 144 | * @param[in] degree_v Degree of the given surface in v-direction. 145 | * @param[in] knots_u Knot vector of the surface in u-direction. 146 | * @param[in] knots_v Knot vector of the surface in v-direction. 147 | * @param[in] control_points Control points of the surface in a 2D array. 148 | * @param[in] num_ders Number of times to differentiate 149 | * @param[in] u Parameter to evaluate the surface at. 150 | * @param[in] v Parameter to evaluate the surface at. 151 | * @param[out] surf_ders Derivatives of the surface at (u, v). 152 | */ 153 | template 154 | array2> 155 | surfaceDerivatives(unsigned int degree_u, unsigned int degree_v, const std::vector &knots_u, 156 | const std::vector &knots_v, const array2> &control_points, 157 | unsigned int num_ders, T u, T v) 158 | { 159 | 160 | array2> surf_ders(num_ders + 1, num_ders + 1, glm::vec(0.0)); 161 | 162 | // Set higher order derivatives to 0 163 | for (int k = degree_u + 1; k <= num_ders; k++) 164 | { 165 | for (int l = degree_v + 1; l <= num_ders; l++) 166 | { 167 | surf_ders(k, l) = glm::vec(0.0); 168 | } 169 | } 170 | 171 | // Find span and basis function derivatives 172 | int span_u = findSpan(degree_u, knots_u, u); 173 | int span_v = findSpan(degree_v, knots_v, v); 174 | array2 ders_u = bsplineDerBasis(degree_u, span_u, knots_u, u, num_ders); 175 | array2 ders_v = bsplineDerBasis(degree_v, span_v, knots_v, v, num_ders); 176 | 177 | // Number of non-zero derivatives is <= degree 178 | unsigned int du = std::min(num_ders, degree_u); 179 | unsigned int dv = std::min(num_ders, degree_v); 180 | 181 | std::vector> temp; 182 | temp.resize(degree_v + 1); 183 | // Compute derivatives 184 | for (int k = 0; k <= du; k++) 185 | { 186 | for (int s = 0; s <= degree_v; s++) 187 | { 188 | temp[s] = glm::vec(0.0); 189 | for (int r = 0; r <= degree_u; r++) 190 | { 191 | temp[s] += static_cast(ders_u(k, r)) * 192 | control_points(span_u - degree_u + r, span_v - degree_v + s); 193 | } 194 | } 195 | 196 | int dd = std::min(num_ders - k, dv); 197 | 198 | for (int l = 0; l <= dd; l++) 199 | { 200 | for (int s = 0; s <= degree_v; s++) 201 | { 202 | surf_ders(k, l) += ders_v(l, s) * temp[s]; 203 | } 204 | } 205 | } 206 | return surf_ders; 207 | } 208 | 209 | } // namespace internal 210 | 211 | ///////////////////////////////////////////////////////////////////// 212 | 213 | /** 214 | Evaluate point on a nonrational NURBS curve 215 | @param[in] crv Curve object 216 | @param[in] u Parameter to evaluate the curve at. 217 | @return point Resulting point on the curve at parameter u. 218 | */ 219 | template glm::vec<3, T> curvePoint(const Curve &crv, T u) 220 | { 221 | return internal::curvePoint(crv.degree, crv.knots, crv.control_points, u); 222 | } 223 | 224 | /** 225 | * Evaluate point on a rational NURBS curve 226 | * @param[in] crv RationalCurve object 227 | * @param[in] u Parameter to evaluate the curve at. 228 | * @return point Resulting point on the curve. 229 | */ 230 | template glm::vec<3, T> curvePoint(const RationalCurve &crv, T u) 231 | { 232 | 233 | typedef glm::vec<4, T> tvecnp1; 234 | 235 | // Compute homogenous coordinates of control points 236 | std::vector Cw; 237 | Cw.reserve(crv.control_points.size()); 238 | for (size_t i = 0; i < crv.control_points.size(); i++) 239 | { 240 | Cw.push_back(tvecnp1(util::cartesianToHomogenous(crv.control_points[i], crv.weights[i]))); 241 | } 242 | 243 | // Compute point using homogenous coordinates 244 | tvecnp1 pointw = internal::curvePoint(crv.degree, crv.knots, Cw, u); 245 | 246 | // Convert back to cartesian coordinates 247 | return util::homogenousToCartesian(pointw); 248 | } 249 | 250 | /** 251 | * Evaluate derivatives of a non-rational NURBS curve 252 | * @param[in] crv Curve object 253 | * @param[in] num_ders Number of times to derivate. 254 | * @param[in] u Parameter to evaluate the derivatives at. 255 | * @return curve_ders Derivatives of the curve at u. 256 | * E.g. curve_ders[n] is the nth derivative at u, where 0 <= n <= num_ders. 257 | */ 258 | template 259 | std::vector> curveDerivatives(const Curve &crv, int num_ders, T u) 260 | { 261 | return internal::curveDerivatives(crv.degree, crv.knots, crv.control_points, num_ders, u); 262 | } 263 | 264 | /** 265 | * Evaluate derivatives of a rational NURBS curve 266 | * @param[in] u Parameter to evaluate the derivatives at. 267 | * @param[in] knots Knot vector of the curve. 268 | * @param[in] control_points Control points of the curve. 269 | * @param[in] weights Weights corresponding to each control point. 270 | * @param[in] num_ders Number of times to differentiate. 271 | * @param[inout] curve_ders Derivatives of the curve at u. 272 | * E.g. curve_ders[n] is the nth derivative at u, where n is between 0 and 273 | * num_ders-1. 274 | */ 275 | template 276 | std::vector> curveDerivatives(const RationalCurve &crv, int num_ders, T u) 277 | { 278 | 279 | typedef glm::vec<3, T> tvecn; 280 | typedef glm::vec<4, T> tvecnp1; 281 | 282 | std::vector curve_ders; 283 | curve_ders.reserve(num_ders + 1); 284 | 285 | // Compute homogenous coordinates of control points 286 | std::vector Cw; 287 | Cw.reserve(crv.control_points.size()); 288 | for (size_t i = 0; i < crv.control_points.size(); i++) 289 | { 290 | Cw.push_back(util::cartesianToHomogenous(crv.control_points[i], crv.weights[i])); 291 | } 292 | 293 | // Derivatives of Cw 294 | std::vector Cwders = 295 | internal::curveDerivatives(crv.degree, crv.knots, Cw, num_ders, u); 296 | 297 | // Split Cwders into coordinates and weights 298 | std::vector Aders; 299 | std::vector wders; 300 | for (const auto &val : Cwders) 301 | { 302 | Aders.push_back(util::truncateHomogenous(val)); 303 | wders.push_back(val.w); 304 | } 305 | 306 | // Compute rational derivatives 307 | for (int k = 0; k <= num_ders; k++) 308 | { 309 | tvecn v = Aders[k]; 310 | for (int i = 1; i <= k; i++) 311 | { 312 | v -= static_cast(util::binomial(k, i)) * wders[i] * curve_ders[k - i]; 313 | } 314 | curve_ders.push_back(v / wders[0]); 315 | } 316 | return curve_ders; 317 | } 318 | 319 | /** 320 | * Evaluate the tangent of a B-spline curve 321 | * @param[in] crv Curve object 322 | * @return Unit tangent of the curve at u. 323 | */ 324 | template glm::vec<3, T> curveTangent(const Curve &crv, T u) 325 | { 326 | std::vector> ders = curveDerivatives(crv, 1, u); 327 | glm::vec<3, T> du = ders[1]; 328 | T du_len = glm::length(du); 329 | if (!util::close(du_len, T(0))) 330 | { 331 | du /= du_len; 332 | } 333 | return du; 334 | } 335 | 336 | /** 337 | * Evaluate the tangent of a rational B-spline curve 338 | * @param[in] crv RationalCurve object 339 | * @return Unit tangent of the curve at u. 340 | */ 341 | template glm::vec<3, T> curveTangent(const RationalCurve &crv, T u) 342 | { 343 | std::vector> ders = curveDerivatives(crv, 1, u); 344 | glm::vec<3, T> du = ders[1]; 345 | T du_len = glm::length(du); 346 | if (!util::close(du_len, T(0))) 347 | { 348 | du /= du_len; 349 | } 350 | return du; 351 | } 352 | 353 | /** 354 | * Evaluate point on a nonrational NURBS surface 355 | * @param[in] srf Surface object 356 | * @param[in] u Parameter to evaluate the surface at. 357 | * @param[in] v Parameter to evaluate the surface at. 358 | * @return Resulting point on the surface at (u, v). 359 | */ 360 | template glm::vec<3, T> surfacePoint(const Surface &srf, T u, T v) 361 | { 362 | return internal::surfacePoint(srf.degree_u, srf.degree_v, srf.knots_u, srf.knots_v, 363 | srf.control_points, u, v); 364 | } 365 | 366 | /** 367 | * Evaluate point on a non-rational NURBS surface 368 | * @param[in] srf RationalSurface object 369 | * @param[in] u Parameter to evaluate the surface at. 370 | * @param[in] v Parameter to evaluate the surface at. 371 | * @return Resulting point on the surface at (u, v). 372 | */ 373 | template glm::vec<3, T> surfacePoint(const RationalSurface &srf, T u, T v) 374 | { 375 | 376 | typedef glm::vec<4, T> tvecnp1; 377 | 378 | // Compute homogenous coordinates of control points 379 | array2 Cw; 380 | Cw.resize(srf.control_points.rows(), srf.control_points.cols()); 381 | for (int i = 0; i < srf.control_points.rows(); i++) 382 | { 383 | for (int j = 0; j < srf.control_points.cols(); j++) 384 | { 385 | Cw(i, j) = 386 | tvecnp1(util::cartesianToHomogenous(srf.control_points(i, j), srf.weights(i, j))); 387 | } 388 | } 389 | 390 | // Compute point using homogenous coordinates 391 | tvecnp1 pointw = 392 | internal::surfacePoint(srf.degree_u, srf.degree_v, srf.knots_u, srf.knots_v, Cw, u, v); 393 | 394 | // Convert back to cartesian coordinates 395 | return util::homogenousToCartesian(pointw); 396 | } 397 | 398 | /** 399 | * Evaluate derivatives on a non-rational NURBS surface 400 | * @param[in] degree_u Degree of the given surface in u-direction. 401 | * @param[in] degree_v Degree of the given surface in v-direction. 402 | * @param[in] knots_u Knot vector of the surface in u-direction. 403 | * @param[in] knots_v Knot vector of the surface in v-direction. 404 | * @param[in] control_points Control points of the surface in a 2D array. 405 | * @param[in] num_ders Number of times to differentiate 406 | * @param[in] u Parameter to evaluate the surface at. 407 | * @param[in] v Parameter to evaluate the surface at. 408 | * @return surf_ders Derivatives of the surface at (u, v). 409 | */ 410 | template 411 | array2> surfaceDerivatives(const Surface &srf, int num_ders, T u, T v) 412 | { 413 | return internal::surfaceDerivatives(srf.degree_u, srf.degree_v, srf.knots_u, srf.knots_v, 414 | srf.control_points, num_ders, u, v); 415 | } 416 | 417 | /** 418 | * Evaluate derivatives on a rational NURBS surface 419 | * @param[in] srf RationalSurface object 420 | * @param[in] u Parameter to evaluate the surface at. 421 | * @param[in] v Parameter to evaluate the surface at. 422 | * @param[in] num_ders Number of times to differentiate 423 | * @return Derivatives on the surface at parameter (u, v). 424 | */ 425 | template 426 | array2> surfaceDerivatives(const RationalSurface &srf, int num_ders, T u, T v) 427 | { 428 | 429 | using namespace std; 430 | using namespace glm; 431 | 432 | typedef vec<3, T> tvecn; 433 | typedef vec<4, T> tvecnp1; 434 | 435 | array2 homo_cp; 436 | homo_cp.resize(srf.control_points.rows(), srf.control_points.cols()); 437 | for (int i = 0; i < srf.control_points.rows(); ++i) 438 | { 439 | for (int j = 0; j < srf.control_points.cols(); ++j) 440 | { 441 | homo_cp(i, j) = 442 | util::cartesianToHomogenous(srf.control_points(i, j), srf.weights(i, j)); 443 | } 444 | } 445 | 446 | array2 homo_ders = internal::surfaceDerivatives( 447 | srf.degree_u, srf.degree_v, srf.knots_u, srf.knots_v, homo_cp, num_ders, u, v); 448 | 449 | array2 Aders; 450 | Aders.resize(num_ders + 1, num_ders + 1); 451 | for (int i = 0; i < homo_ders.rows(); ++i) 452 | { 453 | for (int j = 0; j < homo_ders.cols(); ++j) 454 | { 455 | Aders(i, j) = util::truncateHomogenous(homo_ders(i, j)); 456 | } 457 | } 458 | 459 | array2 surf_ders(num_ders + 1, num_ders + 1); 460 | for (int k = 0; k < num_ders + 1; ++k) 461 | { 462 | for (int l = 0; l < num_ders - k + 1; ++l) 463 | { 464 | auto der = Aders(k, l); 465 | 466 | for (int j = 1; j < l + 1; ++j) 467 | { 468 | der -= (T)util::binomial(l, j) * homo_ders(0, j).w * surf_ders(k, l - j); 469 | } 470 | 471 | for (int i = 1; i < k + 1; ++i) 472 | { 473 | der -= (T)util::binomial(k, i) * homo_ders(i, 0).w * surf_ders(k - i, l); 474 | 475 | tvecn tmp((T)0.0); 476 | for (int j = 1; j < l + 1; ++j) 477 | { 478 | tmp -= (T)util::binomial(l, j) * homo_ders(i, j).w * surf_ders(k - 1, l - j); 479 | } 480 | 481 | der -= (T)util::binomial(k, i) * tmp; 482 | } 483 | 484 | der *= 1 / homo_ders(0, 0).w; 485 | surf_ders(k, l) = der; 486 | } 487 | } 488 | return surf_ders; 489 | } 490 | 491 | /** 492 | * Evaluate the two orthogonal tangents of a non-rational surface at the given 493 | * parameters 494 | * @param[in] srf Surface object 495 | * @param u Parameter in the u-direction 496 | * @param v Parameter in the v-direction 497 | * @return Tuple with unit tangents along u- and v-directions 498 | */ 499 | template 500 | std::tuple, glm::vec<3, T>> surfaceTangent(const Surface &srf, T u, T v) 501 | { 502 | array2> ptder = surfaceDerivatives(srf, 1, u, v); 503 | glm::vec<3, T> du = ptder(1, 0); 504 | glm::vec<3, T> dv = ptder(0, 1); 505 | T du_len = glm::length(ptder(1, 0)); 506 | T dv_len = glm::length(ptder(0, 1)); 507 | if (!util::close(du_len, T(0))) 508 | { 509 | du /= du_len; 510 | } 511 | if (!util::close(dv_len, T(0))) 512 | { 513 | dv /= dv_len; 514 | } 515 | return std::make_tuple(std::move(du), std::move(dv)); 516 | } 517 | 518 | /** 519 | * Evaluate the two orthogonal tangents of a rational surface at the given 520 | * parameters 521 | * @param[in] srf Rational Surface object 522 | * @param u Parameter in the u-direction 523 | * @param v Parameter in the v-direction 524 | * @return Tuple with unit tangents along u- and v-directions 525 | */ 526 | template 527 | std::tuple, glm::vec<3, T>> surfaceTangent(const RationalSurface &srf, T u, T v) 528 | { 529 | array2> ptder = surfaceDerivatives(srf, 1, u, v); 530 | glm::vec<3, T> du = ptder(1, 0); 531 | glm::vec<3, T> dv = ptder(0, 1); 532 | T du_len = glm::length(ptder(1, 0)); 533 | T dv_len = glm::length(ptder(0, 1)); 534 | if (!util::close(du_len, T(0))) 535 | { 536 | du /= du_len; 537 | } 538 | if (!util::close(dv_len, T(0))) 539 | { 540 | dv /= dv_len; 541 | } 542 | return std::make_tuple(std::move(du), std::move(dv)); 543 | } 544 | 545 | /** 546 | * Evaluate the normal a non-rational surface at the given parameters 547 | * @param[in] srf Surface object 548 | * @param u Parameter in the u-direction 549 | * @param v Parameter in the v-direction 550 | * @param[inout] normal Unit normal at of the surface at (u, v) 551 | */ 552 | template glm::vec<3, T> surfaceNormal(const Surface &srf, T u, T v) 553 | { 554 | array2> ptder = surfaceDerivatives(srf, 1, u, v); 555 | glm::vec<3, T> n = glm::cross(ptder(0, 1), ptder(1, 0)); 556 | T n_len = glm::length(n); 557 | if (!util::close(n_len, T(0))) 558 | { 559 | n /= n_len; 560 | } 561 | return n; 562 | } 563 | 564 | /** 565 | * Evaluate the normal of a rational surface at the given parameters 566 | * @param[in] srf Rational Surface object 567 | * @param u Parameter in the u-direction 568 | * @param v Parameter in the v-direction 569 | * @return Unit normal at of the surface at (u, v) 570 | */ 571 | template glm::vec<3, T> surfaceNormal(const RationalSurface &srf, T u, T v) 572 | { 573 | array2> ptder = surfaceDerivatives(srf, 1, u, v); 574 | glm::vec<3, T> n = glm::cross(ptder(0, 1), ptder(1, 0)); 575 | T n_len = glm::length(n); 576 | if (!util::close(n_len, T(0))) 577 | { 578 | n /= n_len; 579 | } 580 | return n; 581 | } 582 | 583 | } // namespace tinynurbs 584 | 585 | #endif // TINYNURBS_EVALUATE_H 586 | -------------------------------------------------------------------------------- /include/tinynurbs/core/modify.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Functions for modifying NURBS curves and surfaces. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be found in 5 | * the LICENSE file. 6 | */ 7 | 8 | #ifndef TINYNURBS_MODIFY_H 9 | #define TINYNURBS_MODIFY_H 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace tinynurbs 20 | { 21 | 22 | ///////////////////////////////////////////////////////////////////// 23 | 24 | namespace internal 25 | { 26 | 27 | /** 28 | * Insert knots in the curve 29 | * @param[in] deg Degree of the curve 30 | * @param[in] knots Knot vector of the curve 31 | * @param[in] cp Control points of the curve 32 | * @param[in] u Parameter to insert knot(s) at 33 | * @param[in] r Number of times to insert knot 34 | * @param[out] new_knots Updated knot vector 35 | * @param[out] new_cp Updated control points 36 | */ 37 | template 38 | void curveKnotInsert(unsigned int deg, const std::vector &knots, 39 | const std::vector> &cp, T u, unsigned int r, 40 | std::vector &new_knots, std::vector> &new_cp) 41 | { 42 | int k = findSpan(deg, knots, u); 43 | unsigned int s = knotMultiplicity(knots, u); 44 | assert(s <= deg); // Multiplicity cannot be greater than degree 45 | if (s == deg) 46 | { 47 | new_knots = knots; 48 | new_cp = cp; 49 | return; 50 | } 51 | if ((r + s) > deg) 52 | { 53 | r = deg - s; 54 | } 55 | 56 | // Insert new knots between k and (k + 1) 57 | new_knots.resize(knots.size() + r); 58 | for (int i = 0; i < k + 1; ++i) 59 | { 60 | new_knots[i] = knots[i]; 61 | } 62 | for (unsigned int i = 1; i < r + 1; ++i) 63 | { 64 | new_knots[k + i] = u; 65 | } 66 | for (int i = k + 1; i < knots.size(); ++i) 67 | { 68 | new_knots[i + r] = knots[i]; 69 | } 70 | // Copy unaffected control points 71 | new_cp.resize(cp.size() + r); 72 | for (int i = 0; i < k - deg + 1; ++i) 73 | { 74 | new_cp[i] = cp[i]; 75 | } 76 | for (int i = k - s; i < cp.size(); ++i) 77 | { 78 | new_cp[i + r] = cp[i]; 79 | } 80 | // Copy affected control points 81 | std::vector> tmp; 82 | tmp.resize(deg - s + 1); 83 | for (int i = 0; i < deg - s + 1; ++i) 84 | { 85 | tmp[i] = cp[k - deg + i]; 86 | } 87 | // Modify affected control points 88 | for (int j = 1; j <= r; ++j) 89 | { 90 | int L = k - deg + j; 91 | for (int i = 0; i < deg - j - s + 1; ++i) 92 | { 93 | T a = (u - knots[L + i]) / (knots[i + k + 1] - knots[L + i]); 94 | tmp[i] = (1 - a) * tmp[i] + a * tmp[i + 1]; 95 | } 96 | new_cp[L] = tmp[0]; 97 | new_cp[k + r - j - s] = tmp[deg - j - s]; 98 | } 99 | int L = k - deg + r; 100 | for (int i = L + 1; i < k - s; ++i) 101 | { 102 | new_cp[i] = tmp[i - L]; 103 | } 104 | } 105 | 106 | /** 107 | * Insert knots in the surface along one direction 108 | * @param[in] degree Degree of the surface along which to insert knot 109 | * @param[in] knots Knot vector 110 | * @param[in] cp 2D array of control points 111 | * @param[in] knot Knot value to insert 112 | * @param[in] r Number of times to insert 113 | * @param[in] along_u Whether inserting along u-direction 114 | * @param[out] new_knots Updated knot vector 115 | * @param[out] new_cp Updated control points 116 | */ 117 | template 118 | void surfaceKnotInsert(unsigned int degree, const std::vector &knots, 119 | const array2> &cp, T knot, unsigned int r, bool along_u, 120 | std::vector &new_knots, array2> &new_cp) 121 | { 122 | int span = findSpan(degree, knots, knot); 123 | unsigned int s = knotMultiplicity(knots, knot); 124 | assert(s <= degree); // Knot multiplicity cannot be greater than degree 125 | if (s == degree) 126 | { 127 | new_cp = cp; 128 | new_knots = knots; 129 | return; 130 | } 131 | if ((r + s) > degree) 132 | { 133 | r = degree - s; 134 | } 135 | 136 | // Create a new knot vector 137 | new_knots.resize(knots.size() + r); 138 | for (int i = 0; i <= span; ++i) 139 | { 140 | new_knots[i] = knots[i]; 141 | } 142 | for (int i = 1; i <= r; ++i) 143 | { 144 | new_knots[span + i] = knot; 145 | } 146 | for (int i = span + 1; i < knots.size(); ++i) 147 | { 148 | new_knots[i + r] = knots[i]; 149 | } 150 | // Compute alpha 151 | array2 alpha(degree - s, r + 1, T(0)); 152 | for (int j = 1; j <= r; ++j) 153 | { 154 | int L = span - degree + j; 155 | for (int i = 0; i <= degree - j - s; ++i) 156 | { 157 | alpha(i, j) = (knot - knots[L + i]) / (knots[i + span + 1] - knots[L + i]); 158 | } 159 | } 160 | 161 | // Create a temporary container for affected control points per row/column 162 | std::vector> tmp(degree + 1); 163 | 164 | if (along_u) 165 | { 166 | // Create new control points with additional rows 167 | new_cp.resize(cp.rows() + r, cp.cols()); 168 | 169 | // Update control points 170 | // Each row is a u-isocurve, each col is a v-isocurve 171 | for (int col = 0; col < cp.cols(); ++col) 172 | { 173 | // Copy unaffected control points 174 | for (int i = 0; i <= span - degree; ++i) 175 | { 176 | new_cp(i, col) = cp(i, col); 177 | } 178 | for (int i = span - s; i < cp.rows(); ++i) 179 | { 180 | new_cp(i + r, col) = cp(i, col); 181 | } 182 | // Copy affected control points to temp array 183 | for (int i = 0; i < degree - s + 1; ++i) 184 | { 185 | tmp[i] = cp(span - degree + i, col); 186 | } 187 | // Insert knot 188 | for (int j = 1; j <= r; ++j) 189 | { 190 | int L = span - degree + j; 191 | for (int i = 0; i <= degree - j - s; ++i) 192 | { 193 | T a = alpha(i, j); 194 | tmp[i] = (1 - a) * tmp[i] + a * tmp[i + 1]; 195 | } 196 | new_cp(L, col) = tmp[0]; 197 | new_cp(span + r - j - s, col) = tmp[degree - j - s]; 198 | } 199 | int L = span - degree + r; 200 | for (int i = L + 1; i < span - s; ++i) 201 | { 202 | new_cp(i, col) = tmp[i - L]; 203 | } 204 | } 205 | } 206 | else 207 | { 208 | // Create new control points with additional columns 209 | new_cp.resize(cp.rows(), cp.cols() + r); 210 | 211 | // Update control points 212 | // Each row is a u-isocurve, each col is a v-isocurve 213 | for (int row = 0; row < cp.rows(); ++row) 214 | { 215 | // Copy unaffected control points 216 | for (int i = 0; i <= span - degree; ++i) 217 | { 218 | new_cp(row, i) = cp(row, i); 219 | } 220 | for (int i = span - s; i < cp.cols(); ++i) 221 | { 222 | new_cp(row, i + r) = cp(row, i); 223 | } 224 | // Copy affected control points to temp array 225 | for (int i = 0; i < degree - s + 1; ++i) 226 | { 227 | tmp[i] = cp(row, span - degree + i); 228 | } 229 | // Insert knot 230 | for (int j = 1; j <= r; ++j) 231 | { 232 | int L = span - degree + j; 233 | for (int i = 0; i <= degree - j - s; ++i) 234 | { 235 | T a = alpha(i, j); 236 | tmp[i] = (1 - a) * tmp[i] + a * tmp[i + 1]; 237 | } 238 | new_cp(row, L) = tmp[0]; 239 | new_cp(row, span + r - j - s) = tmp[degree - j - s]; 240 | } 241 | int L = span - degree + r; 242 | for (int i = L + 1; i < span - s; ++i) 243 | { 244 | new_cp(row, i) = tmp[i - L]; 245 | } 246 | } 247 | } 248 | } 249 | 250 | /** 251 | * Split the curve into two 252 | * @param[in] degree Degree of curve 253 | * @param[in] knots Knot vector 254 | * @param[in] control_points Array of control points 255 | * @param[in] u Parameter to split curve 256 | * @param[out] left_knots Knots of the left part of the curve 257 | * @param[out] left_control_points Control points of the left part of the curve 258 | * @param[out] right_knots Knots of the right part of the curve 259 | * @param[out] right_control_points Control points of the right part of the curve 260 | */ 261 | template 262 | void curveSplit(unsigned int degree, const std::vector &knots, 263 | const std::vector> &control_points, T u, 264 | std::vector &left_knots, std::vector> &left_control_points, 265 | std::vector &right_knots, std::vector> &right_control_points) 266 | { 267 | std::vector tmp_knots; 268 | std::vector> tmp_cp; 269 | 270 | int span = findSpan(degree, knots, u); 271 | int r = degree - knotMultiplicity(knots, u); 272 | 273 | internal::curveKnotInsert(degree, knots, control_points, u, r, tmp_knots, tmp_cp); 274 | 275 | left_knots.clear(); 276 | right_knots.clear(); 277 | left_control_points.clear(); 278 | right_control_points.clear(); 279 | 280 | int span_l = findSpan(degree, tmp_knots, u) + 1; 281 | for (int i = 0; i < span_l; ++i) 282 | { 283 | left_knots.push_back(tmp_knots[i]); 284 | } 285 | left_knots.push_back(u); 286 | 287 | for (int i = 0; i < degree + 1; ++i) 288 | { 289 | right_knots.push_back(u); 290 | } 291 | for (int i = span_l; i < tmp_knots.size(); ++i) 292 | { 293 | right_knots.push_back(tmp_knots[i]); 294 | } 295 | 296 | int ks = span - degree + 1; 297 | for (int i = 0; i < ks + r; ++i) 298 | { 299 | left_control_points.push_back(tmp_cp[i]); 300 | } 301 | for (int i = ks + r - 1; i < tmp_cp.size(); ++i) 302 | { 303 | right_control_points.push_back(tmp_cp[i]); 304 | } 305 | } 306 | 307 | /** 308 | * Split the surface into two along given parameter direction 309 | * @param[in] degree Degree of surface along given direction 310 | * @param[in] knots Knot vector of surface along given direction 311 | * @param[in] control_points Array of control points 312 | * @param[in] param Parameter to split curve 313 | * @param[in] along_u Whether the direction to split along is the u-direction 314 | * @param[out] left_knots Knots of the left part of the curve 315 | * @param[out] left_control_points Control points of the left part of the curve 316 | * @param[out] right_knots Knots of the right part of the curve 317 | * @param[out] right_control_points Control points of the right part of the curve 318 | */ 319 | template 320 | void surfaceSplit(unsigned int degree, const std::vector &knots, 321 | const array2> &control_points, T param, bool along_u, 322 | std::vector &left_knots, array2> &left_control_points, 323 | std::vector &right_knots, array2> &right_control_points) 324 | { 325 | std::vector tmp_knots; 326 | array2> tmp_cp; 327 | 328 | int span = findSpan(degree, knots, param); 329 | unsigned int r = degree - knotMultiplicity(knots, param); 330 | internal::surfaceKnotInsert(degree, knots, control_points, param, r, along_u, tmp_knots, 331 | tmp_cp); 332 | 333 | left_knots.clear(); 334 | right_knots.clear(); 335 | 336 | int span_l = findSpan(degree, tmp_knots, param) + 1; 337 | for (int i = 0; i < span_l; ++i) 338 | { 339 | left_knots.push_back(tmp_knots[i]); 340 | } 341 | left_knots.push_back(param); 342 | 343 | for (int i = 0; i < degree + 1; ++i) 344 | { 345 | right_knots.push_back(param); 346 | } 347 | for (int i = span_l; i < tmp_knots.size(); ++i) 348 | { 349 | right_knots.push_back(tmp_knots[i]); 350 | } 351 | 352 | int ks = span - degree + 1; 353 | if (along_u) 354 | { 355 | size_t ii = 0; 356 | left_control_points.resize(ks + r, tmp_cp.cols()); 357 | for (int i = 0; i < ks + r; ++i) 358 | { 359 | for (int j = 0; j < tmp_cp.cols(); ++j) 360 | { 361 | left_control_points[ii++] = tmp_cp(i, j); 362 | } 363 | } 364 | ii = 0; 365 | right_control_points.resize(tmp_cp.rows() - ks - r + 1, tmp_cp.cols()); 366 | for (int i = ks + r - 1; i < tmp_cp.rows(); ++i) 367 | { 368 | for (int j = 0; j < tmp_cp.cols(); ++j) 369 | { 370 | right_control_points[ii++] = tmp_cp(i, j); 371 | } 372 | } 373 | } 374 | else 375 | { 376 | size_t ii = 0; 377 | left_control_points.resize(tmp_cp.rows(), ks + r); 378 | for (int i = 0; i < tmp_cp.rows(); ++i) 379 | { 380 | for (int j = 0; j < ks + r; ++j) 381 | { 382 | left_control_points[ii++] = tmp_cp(i, j); 383 | } 384 | } 385 | ii = 0; 386 | right_control_points.resize(tmp_cp.rows(), tmp_cp.cols() - ks - r + 1); 387 | for (int i = 0; i < tmp_cp.rows(); ++i) 388 | { 389 | for (int j = ks + r - 1; j < tmp_cp.cols(); ++j) 390 | { 391 | right_control_points[ii++] = tmp_cp(i, j); 392 | } 393 | } 394 | } 395 | } 396 | 397 | } // namespace internal 398 | 399 | ///////////////////////////////////////////////////////////////////// 400 | 401 | /** 402 | * Insert knots in the curve 403 | * @param[in] crv Curve object 404 | * @param[in] u Parameter to insert knot at 405 | * @param[in] repeat Number of times to insert 406 | * @return New curve with #repeat knots inserted at u 407 | */ 408 | template Curve curveKnotInsert(const Curve &crv, T u, unsigned int repeat = 1) 409 | { 410 | Curve new_crv; 411 | new_crv.degree = crv.degree; 412 | internal::curveKnotInsert(crv.degree, crv.knots, crv.control_points, u, repeat, new_crv.knots, 413 | new_crv.control_points); 414 | return new_crv; 415 | } 416 | 417 | /** 418 | * Insert knots in the rational curve 419 | * @param[in] crv RationalCurve object 420 | * @param[in] u Parameter to insert knot at 421 | * @param[in] repeat Number of times to insert 422 | * @return New RationalCurve object with #repeat knots inserted at u 423 | */ 424 | template 425 | RationalCurve curveKnotInsert(const RationalCurve &crv, T u, unsigned int repeat = 1) 426 | { 427 | RationalCurve new_crv; 428 | new_crv.degree = crv.degree; 429 | 430 | // Convert to homogenous coordinates 431 | std::vector> Cw; 432 | Cw.reserve(crv.control_points.size()); 433 | for (int i = 0; i < crv.control_points.size(); ++i) 434 | { 435 | Cw.push_back(util::cartesianToHomogenous(crv.control_points[i], crv.weights[i])); 436 | } 437 | 438 | // Perform knot insertion and get new knots and control points 439 | std::vector> new_Cw; 440 | std::vector new_knots; 441 | internal::curveKnotInsert(crv.degree, crv.knots, Cw, u, repeat, new_crv.knots, new_Cw); 442 | 443 | // Convert back to cartesian coordinates 444 | new_crv.control_points.reserve(new_Cw.size()); 445 | new_crv.weights.reserve(new_Cw.size()); 446 | for (int i = 0; i < new_Cw.size(); ++i) 447 | { 448 | new_crv.control_points.push_back(util::homogenousToCartesian(new_Cw[i])); 449 | new_crv.weights.push_back(new_Cw[i].w); 450 | } 451 | return new_crv; 452 | } 453 | 454 | /** 455 | * Insert knots in the surface along u-direction 456 | * @param[in] srf Surface object 457 | * @param[in] u Knot value to insert 458 | * @param[in] repeat Number of times to insert 459 | * @return New Surface object after knot insertion 460 | */ 461 | template 462 | Surface surfaceKnotInsertU(const Surface &srf, T u, unsigned int repeat = 1) 463 | { 464 | Surface new_srf; 465 | new_srf.degree_u = srf.degree_u; 466 | new_srf.degree_v = srf.degree_v; 467 | new_srf.knots_v = srf.knots_v; 468 | internal::surfaceKnotInsert(new_srf.degree_u, srf.knots_u, srf.control_points, u, repeat, true, 469 | new_srf.knots_u, new_srf.control_points); 470 | return new_srf; 471 | } 472 | 473 | /** 474 | * Insert knots in the rational surface along u-direction 475 | * @param[in] srf RationalSurface object 476 | * @param[in] u Knot value to insert 477 | * @param[in] repeat Number of times to insert 478 | * @return New RationalSurface object after knot insertion 479 | */ 480 | template 481 | RationalSurface surfaceKnotInsertU(const RationalSurface &srf, T u, unsigned int repeat = 1) 482 | { 483 | RationalSurface new_srf; 484 | new_srf.degree_u = srf.degree_u; 485 | new_srf.degree_v = srf.degree_v; 486 | new_srf.knots_v = srf.knots_v; 487 | 488 | // Original control points in homogenous coordinates 489 | array2> Cw(srf.control_points.rows(), srf.control_points.cols()); 490 | for (int i = 0; i < srf.control_points.rows(); ++i) 491 | { 492 | for (int j = 0; j < srf.control_points.cols(); ++j) 493 | { 494 | Cw(i, j) = util::cartesianToHomogenous(srf.control_points(i, j), srf.weights(i, j)); 495 | } 496 | } 497 | 498 | // New knots and new homogenous control points after knot insertion 499 | std::vector new_knots_u; 500 | array2> new_Cw; 501 | internal::surfaceKnotInsert(srf.degree_u, srf.knots_u, Cw, u, repeat, true, new_srf.knots_u, 502 | new_Cw); 503 | 504 | // Convert back to cartesian coordinates 505 | new_srf.control_points.resize(new_Cw.rows(), new_Cw.cols()); 506 | new_srf.weights.resize(new_Cw.rows(), new_Cw.cols()); 507 | for (int i = 0; i < new_Cw.rows(); ++i) 508 | { 509 | for (int j = 0; j < new_Cw.cols(); ++j) 510 | { 511 | new_srf.control_points(i, j) = util::homogenousToCartesian(new_Cw(i, j)); 512 | new_srf.weights(i, j) = new_Cw(i, j).w; 513 | } 514 | } 515 | return new_srf; 516 | } 517 | 518 | /** 519 | * Insert knots in the surface along v-direction 520 | * @param[in] srf Surface object 521 | * @param[in] v Knot value to insert 522 | * @param[in] repeat Number of times to insert 523 | * @return New Surface object after knot insertion 524 | */ 525 | template 526 | Surface surfaceKnotInsertV(const Surface &srf, T v, unsigned int repeat = 1) 527 | { 528 | Surface new_srf; 529 | new_srf.degree_u = srf.degree_u; 530 | new_srf.degree_v = srf.degree_v; 531 | new_srf.knots_u = srf.knots_u; 532 | // New knots and new control points after knot insertion 533 | internal::surfaceKnotInsert(srf.degree_v, srf.knots_v, srf.control_points, v, repeat, false, 534 | new_srf.knots_v, new_srf.control_points); 535 | return new_srf; 536 | } 537 | 538 | /** 539 | * Insert knots in the rational surface along v-direction 540 | * @param[in] srf RationalSurface object 541 | * @param[in] v Knot value to insert 542 | * @param[in] repeat Number of times to insert 543 | * @return New RationalSurface object after knot insertion 544 | */ 545 | template 546 | RationalSurface surfaceKnotInsertV(const RationalSurface &srf, T v, unsigned int repeat = 1) 547 | { 548 | RationalSurface new_srf; 549 | new_srf.degree_u = srf.degree_u; 550 | new_srf.degree_v = srf.degree_v; 551 | new_srf.knots_u = srf.knots_u; 552 | // Original control points in homogenous coordinates 553 | array2> Cw(srf.control_points.rows(), srf.control_points.cols()); 554 | for (int i = 0; i < srf.control_points.rows(); ++i) 555 | { 556 | for (int j = 0; j < srf.control_points.cols(); ++j) 557 | { 558 | Cw(i, j) = util::cartesianToHomogenous(srf.control_points(i, j), srf.weights(i, j)); 559 | } 560 | } 561 | 562 | // New knots and new homogenous control points after knot insertion 563 | std::vector new_knots_v; 564 | array2> new_Cw; 565 | internal::surfaceKnotInsert(srf.degree_v, srf.knots_v, Cw, v, repeat, false, new_srf.knots_v, 566 | new_Cw); 567 | 568 | // Convert back to cartesian coordinates 569 | new_srf.control_points.resize(new_Cw.rows(), new_Cw.cols()); 570 | new_srf.weights.resize(new_Cw.rows(), new_Cw.cols()); 571 | for (int i = 0; i < new_Cw.rows(); ++i) 572 | { 573 | for (int j = 0; j < new_Cw.cols(); ++j) 574 | { 575 | new_srf.control_points(i, j) = util::homogenousToCartesian(new_Cw(i, j)); 576 | new_srf.weights(i, j) = new_Cw(i, j).w; 577 | } 578 | } 579 | return new_srf; 580 | } 581 | 582 | /** 583 | * Split a curve into two 584 | * @param[in] crv Curve object 585 | * @param[in] u Parameter to split at 586 | * @return Tuple with first half and second half of the curve 587 | */ 588 | template std::tuple, Curve> curveSplit(const Curve &crv, T u) 589 | { 590 | Curve left, right; 591 | left.degree = crv.degree; 592 | right.degree = crv.degree; 593 | internal::curveSplit(crv.degree, crv.knots, crv.control_points, u, left.knots, 594 | left.control_points, right.knots, right.control_points); 595 | return std::make_tuple(std::move(left), std::move(right)); 596 | } 597 | 598 | /** 599 | * Split a rational curve into two 600 | * @param[in] crv RationalCurve object 601 | * @param[in] u Parameter to split at 602 | * @return Tuple with first half and second half of the curve 603 | */ 604 | template 605 | std::tuple, RationalCurve> curveSplit(const RationalCurve &crv, T u) 606 | { 607 | RationalCurve left, right; 608 | left.degree = crv.degree; 609 | right.degree = crv.degree; 610 | 611 | std::vector> Cw, left_Cw, right_Cw; 612 | Cw.reserve(crv.control_points.size()); 613 | for (int i = 0; i < crv.control_points.size(); ++i) 614 | { 615 | Cw.push_back(util::cartesianToHomogenous(crv.control_points[i], crv.weights[i])); 616 | } 617 | 618 | internal::curveSplit(crv.degree, crv.knots, Cw, u, left.knots, left_Cw, right.knots, right_Cw); 619 | 620 | left.control_points.reserve(left_Cw.size()); 621 | left.weights.reserve(left_Cw.size()); 622 | right.control_points.reserve(right_Cw.size()); 623 | right.weights.reserve(right_Cw.size()); 624 | for (int i = 0; i < left_Cw.size(); ++i) 625 | { 626 | left.control_points.push_back(util::homogenousToCartesian(left_Cw[i])); 627 | left.weights.push_back(left_Cw[i].w); 628 | } 629 | for (int i = 0; i < right_Cw.size(); ++i) 630 | { 631 | right.control_points.push_back(util::homogenousToCartesian(right_Cw[i])); 632 | right.weights.push_back(right_Cw[i].w); 633 | } 634 | return std::make_tuple(std::move(left), std::move(right)); 635 | } 636 | 637 | /** 638 | * Split a surface into two along u-direction 639 | * @param[in] srf Surface object 640 | * @param[in] u Parameter along u-direction to split the surface 641 | * @return Tuple with first and second half of the surfaces 642 | */ 643 | template std::tuple, Surface> surfaceSplitU(const Surface &srf, T u) 644 | { 645 | Surface left, right; 646 | left.degree_u = srf.degree_u; 647 | left.degree_v = srf.degree_v; 648 | left.knots_v = srf.knots_v; 649 | right.degree_u = srf.degree_u; 650 | right.degree_v = srf.degree_v; 651 | right.knots_v = srf.knots_v; 652 | internal::surfaceSplit(srf.degree_u, srf.knots_u, srf.control_points, u, true, left.knots_u, 653 | left.control_points, right.knots_u, right.control_points); 654 | return std::make_tuple(std::move(left), std::move(right)); 655 | } 656 | 657 | /** 658 | * Split a rational surface into two along u-direction 659 | * @param[in] srf RationalSurface object 660 | * @param[in] u Parameter along u-direction to split the surface 661 | * @return Tuple with first and second half of the surfaces 662 | */ 663 | template 664 | std::tuple, RationalSurface> surfaceSplitU(const RationalSurface &srf, T u) 665 | { 666 | RationalSurface left, right; 667 | left.degree_u = srf.degree_u; 668 | left.degree_v = srf.degree_v; 669 | left.knots_v = srf.knots_v; 670 | right.degree_u = srf.degree_u; 671 | right.degree_v = srf.degree_v; 672 | right.knots_v = srf.knots_v; 673 | 674 | // Compute homogenous coordinates of control points and weights 675 | array2> Cw = util::cartesianToHomogenous(srf.control_points, srf.weights); 676 | 677 | // Split surface with homogenous coordinates 678 | array2> left_Cw, right_Cw; 679 | internal::surfaceSplit(srf.degree_u, srf.knots_u, Cw, u, true, left.knots_u, left_Cw, 680 | right.knots_u, right_Cw); 681 | 682 | // Convert back to cartesian coordinates 683 | util::homogenousToCartesian(left_Cw, left.control_points, left.weights); 684 | util::homogenousToCartesian(right_Cw, right.control_points, right.weights); 685 | 686 | return std::make_tuple(std::move(left), std::move(right)); 687 | } 688 | 689 | /** 690 | * Split a surface into two along v-direction 691 | * @param[in] srf Surface object 692 | * @param[in] v Parameter along v-direction to split the surface 693 | * @return Tuple with first and second half of the surfaces 694 | */ 695 | template std::tuple, Surface> surfaceSplitV(const Surface &srf, T v) 696 | { 697 | Surface left, right; 698 | left.degree_u = srf.degree_u; 699 | left.degree_v = srf.degree_v; 700 | left.knots_u = srf.knots_u; 701 | right.degree_u = srf.degree_u; 702 | right.degree_v = srf.degree_v; 703 | right.knots_u = srf.knots_u; 704 | internal::surfaceSplit(srf.degree_v, srf.knots_v, srf.control_points, v, false, left.knots_v, 705 | left.control_points, right.knots_v, right.control_points); 706 | return std::make_tuple(std::move(left), std::move(right)); 707 | } 708 | 709 | /** 710 | * Split a rational surface into two along v-direction 711 | * @param[in] srf RationalSurface object 712 | * @param[in] v Parameter along v-direction to split the surface 713 | * @return Tuple with first and second half of the surfaces 714 | */ 715 | template 716 | std::tuple, RationalSurface> surfaceSplitV(const RationalSurface &srf, T v) 717 | { 718 | RationalSurface left, right; 719 | left.degree_u = srf.degree_u; 720 | left.degree_v = srf.degree_v; 721 | left.knots_u = srf.knots_u; 722 | right.degree_u = srf.degree_u; 723 | right.degree_v = srf.degree_v; 724 | right.knots_u = srf.knots_u; 725 | 726 | // Compute homogenous coordinates of control points and weights 727 | array2> Cw = util::cartesianToHomogenous(srf.control_points, srf.weights); 728 | 729 | // Split surface with homogenous coordinates 730 | array2> left_Cw, right_Cw; 731 | internal::surfaceSplit(srf.degree_v, srf.knots_v, Cw, v, false, left.knots_v, left_Cw, 732 | right.knots_v, right_Cw); 733 | 734 | // Convert back to cartesian coordinates 735 | util::homogenousToCartesian(left_Cw, left.control_points, left.weights); 736 | util::homogenousToCartesian(right_Cw, right.control_points, right.weights); 737 | 738 | return std::make_tuple(std::move(left), std::move(right)); 739 | } 740 | 741 | } // namespace tinynurbs 742 | 743 | #endif // TINYNURBS_MODIFY_H 744 | -------------------------------------------------------------------------------- /include/tinynurbs/core/surface.h: -------------------------------------------------------------------------------- 1 | /** 2 | * The Surface and RationalSurface classes represent non-rational and rational 3 | * NURBS surfaces, respectively. 4 | * 5 | * Use of this source code is governed by a BSD-style license that can be found in 6 | * the LICENSE file. 7 | */ 8 | 9 | #ifndef TINYNURBS_SURFACE_H 10 | #define TINYNURBS_SURFACE_H 11 | 12 | #include "../util/array2.h" 13 | #include "glm/glm.hpp" 14 | #include 15 | #include 16 | 17 | namespace tinynurbs 18 | { 19 | 20 | // Forward declaration 21 | template struct RationalSurface; 22 | 23 | /** 24 | Struct for representing a non-rational NURBS surface 25 | \tparam T Data type of control points and weights (float or double) 26 | */ 27 | template struct Surface 28 | { 29 | unsigned int degree_u, degree_v; 30 | std::vector knots_u, knots_v; 31 | array2> control_points; 32 | 33 | Surface() = default; 34 | Surface(const RationalSurface &srf) 35 | : degree_u(srf.degree_u), degree_v(srf.degree_v), knots_u(srf.knots_u), 36 | knots_v(srf.knots_v), control_points(srf.control_points) 37 | { 38 | } 39 | Surface(unsigned int degree_u, unsigned int degree_v, const std::vector &knots_u, 40 | const std::vector &knots_v, array2> control_points) 41 | : degree_u(degree_u), degree_v(degree_v), knots_u(knots_u), knots_v(knots_v), 42 | control_points(control_points) 43 | { 44 | } 45 | }; 46 | 47 | /** 48 | Struct for representing a non-rational NURBS surface 49 | \tparam T Data type of control points and weights (float or double) 50 | */ 51 | template struct RationalSurface 52 | { 53 | unsigned int degree_u, degree_v; 54 | std::vector knots_u, knots_v; 55 | array2> control_points; 56 | array2 weights; 57 | 58 | RationalSurface() = default; 59 | RationalSurface(const Surface &srf, const array2 &weights) 60 | : degree_u(srf.degree_u), degree_v(srf.degree_v), knots_u(srf.knots_u), 61 | knots_v(srf.knots_v), control_points(srf.control_points), weights(weights) 62 | { 63 | } 64 | RationalSurface(const Surface &srf) 65 | : RationalSurface(srf, array2(srf.control_points.rows(), srf.control_points.cols(), 1.0)) 66 | { 67 | } 68 | RationalSurface(unsigned int degree_u, unsigned int degree_v, const std::vector &knots_u, 69 | const std::vector &knots_v, const array2> &control_points, 70 | const array2 &weights) 71 | : degree_u(degree_u), degree_v(degree_v), knots_u(knots_u), knots_v(knots_v), 72 | control_points(control_points), weights(weights) 73 | { 74 | } 75 | }; 76 | 77 | // Typedefs for ease of use 78 | typedef Surface Surface3f; 79 | typedef Surface Surface3d; 80 | typedef RationalSurface RationalSurface3f; 81 | typedef RationalSurface RationalSurface3d; 82 | 83 | } // namespace tinynurbs 84 | 85 | #endif // TINYNURBS_SURFACE_H 86 | -------------------------------------------------------------------------------- /include/tinynurbs/io/obj.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Wavefront OBJ realted I/O functionality for curves and surfaces. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be found in 5 | * the LICENSE file. 6 | */ 7 | 8 | #ifndef TINYNURBS_OBJ_H 9 | #define TINYNURBS_OBJ_H 10 | 11 | #include "../core/curve.h" 12 | #include "../core/surface.h" 13 | #include "../util/array2.h" 14 | #include "../util/util.h" 15 | #include "glm/glm.hpp" 16 | #include 17 | #include 18 | #include 19 | 20 | namespace tinynurbs 21 | { 22 | 23 | ///////////////////////////////////////////////////////////////////// 24 | 25 | namespace internal 26 | { 27 | 28 | /** 29 | * Read rational curve data from a Wavefront OBJ stream. 30 | * @param is The input stream 31 | * @param[inout] deg Degree of the curve 32 | * @param[inout] knots Knot vector of the curve 33 | * @param[inout] ctrlPts Array of control points 34 | * @param[inout] weights Array of corresponding weights 35 | * @param[inout] rational Whether rational 36 | */ 37 | template 38 | void curveReadOBJ(std::istream &is, unsigned int °, std::vector &knots, 39 | std::vector> &ctrlPts, std::vector &weights, bool &rational) 40 | { 41 | T knot_min = 0, knot_max = 1; 42 | std::vector> ctrl_pts_buf; 43 | std::vector weights_buf; 44 | std::vector indices; 45 | std::vector temp_knots; 46 | 47 | std::string start, token, sline; 48 | std::istringstream ssline; 49 | 50 | struct ToParse 51 | { 52 | bool deg, cstype, curv, parm; 53 | }; 54 | 55 | ToParse parsed; 56 | 57 | while (std::getline(is, sline)) 58 | { 59 | if (sline.size() == 0) 60 | { 61 | continue; 62 | } 63 | ssline.str(sline); 64 | ssline >> start; 65 | if (start == "v") 66 | { 67 | std::vector four_coords; 68 | four_coords.resize(4, 0.0); 69 | four_coords[3] = 1.0; 70 | int index = 0; 71 | while (ssline && index <= 3) 72 | { 73 | ssline >> four_coords[index++]; 74 | } 75 | ctrl_pts_buf.emplace_back(four_coords[0], four_coords[1], four_coords[2]); 76 | weights_buf.push_back(four_coords[3]); 77 | } 78 | else if (start == "cstype") 79 | { 80 | std::string token1; 81 | ssline >> token1; 82 | if (token1 == "bspline") 83 | { 84 | rational = false; 85 | parsed.cstype = true; 86 | } 87 | else if (token1 == "rat") 88 | { 89 | std::string token2; 90 | ssline >> token2; 91 | if (token2 == "bspline") 92 | { 93 | rational = true; 94 | parsed.cstype = true; 95 | } 96 | } 97 | } 98 | else if (start == "deg") 99 | { 100 | ssline >> deg; 101 | parsed.deg = true; 102 | } 103 | else if (start == "curv") 104 | { 105 | ssline >> knot_min >> knot_max; 106 | while (ssline >> token) 107 | { 108 | if (token == "\\") 109 | { 110 | ssline.clear(); 111 | getline(is, sline); 112 | ssline.str(sline); 113 | } 114 | else 115 | { 116 | indices.push_back(std::stof(token)); 117 | } 118 | } 119 | parsed.curv = true; 120 | } 121 | else if (start == "parm") 122 | { 123 | ssline >> start; 124 | if (start == "u") 125 | { 126 | while (ssline >> token) 127 | { 128 | if (token == "\\") 129 | { 130 | ssline.clear(); 131 | std::getline(is, sline); 132 | ssline.str(sline); 133 | } 134 | else 135 | { 136 | temp_knots.push_back(std::stof(token)); 137 | } 138 | } 139 | } 140 | parsed.parm = true; 141 | } 142 | else if (start == "end") 143 | { 144 | break; 145 | } 146 | ssline.clear(); 147 | } 148 | 149 | // Check if necessary data was available in stream 150 | if (!parsed.cstype) 151 | { 152 | throw std::runtime_error("'cstype bspline / cstype rat bspline' line missing in file"); 153 | } 154 | if (!parsed.deg) 155 | { 156 | throw std::runtime_error("'deg' line missing/incomplete in file"); 157 | } 158 | if (!parsed.curv) 159 | { 160 | throw std::runtime_error("'curv' line missing/incomplete in file"); 161 | } 162 | if (!parsed.parm) 163 | { 164 | throw std::runtime_error("'parm' line missing/incomplete in file"); 165 | } 166 | 167 | int num_knots = temp_knots.size(); 168 | int num_cp = num_knots - deg - 1; 169 | 170 | ctrlPts.resize(num_cp); 171 | weights.resize(num_cp); 172 | size_t num = 0; 173 | for (int i = 0; i < num_cp; ++i) 174 | { 175 | assert(i < ctrlPts.size()); 176 | ctrlPts[i] = ctrl_pts_buf[indices[num] - 1]; 177 | weights[i] = weights_buf[indices[num] - 1]; 178 | ++num; 179 | } 180 | 181 | knots = temp_knots; 182 | } 183 | 184 | /** 185 | * Read rational surface data from a Wavefront OBJ stream. 186 | * @param is The input stream 187 | * @param[inout] deg_u Degree of the surface along u-direction 188 | * @param[inout] deg_v Degree of the surface along u-direction 189 | * @param[inout] knots_u Knot vector of the surface along u-direction 190 | * @param[inout] knots_v Knot vector of the surface along v-direction 191 | * @param[inout] ctrlPts 2D grid of control points of the surface 192 | * @param[inout] weights 2D grid of corresponding weights 193 | * @param[inout] rational Whether rational 194 | */ 195 | template 196 | void surfaceReadOBJ(std::istream &is, unsigned int °_u, unsigned int °_v, 197 | std::vector &knots_u, std::vector &knots_v, 198 | array2> &ctrlPts, array2 &weights, bool &rational) 199 | { 200 | T uknot_min = 0, uknot_max = 1; 201 | T vknot_min = 0, vknot_max = 1; 202 | 203 | std::vector> ctrl_pts_buf; 204 | std::vector weights_buf; 205 | std::vector indices; 206 | std::vector temp_uknots; 207 | std::vector temp_vknots; 208 | 209 | std::string start, token, sline; 210 | std::istringstream ssline; 211 | 212 | struct ToParse 213 | { 214 | bool deg, cstype, surf, parm; 215 | }; 216 | 217 | ToParse parsed; 218 | 219 | while (std::getline(is, sline)) 220 | { 221 | if (sline.size() == 0) 222 | { 223 | break; 224 | } 225 | ssline.str(sline); 226 | ssline >> start; 227 | if (start == "v") 228 | { 229 | std::vector four_coords; 230 | four_coords.resize(4); 231 | four_coords[3] = 1.0; 232 | int index = 0; 233 | while (ssline && index <= 3) 234 | { 235 | ssline >> four_coords[index++]; 236 | } 237 | ctrl_pts_buf.emplace_back(four_coords[0], four_coords[1], four_coords[2]); 238 | weights_buf.push_back(four_coords[3]); 239 | } 240 | else if (start == "cstype") 241 | { 242 | std::string token1; 243 | ssline >> token1; 244 | if (token1 == "bspline") 245 | { 246 | rational = false; 247 | parsed.cstype = true; 248 | } 249 | else if (token1 == "rat") 250 | { 251 | std::string token2; 252 | ssline >> token2; 253 | if (token2 == "bspline") 254 | { 255 | rational = true; 256 | parsed.cstype = true; 257 | } 258 | } 259 | } 260 | else if (start == "deg") 261 | { 262 | ssline >> deg_u >> deg_v; 263 | parsed.deg = true; 264 | } 265 | else if (start == "surf") 266 | { 267 | ssline >> uknot_min >> uknot_max >> vknot_min >> vknot_max; 268 | while (ssline >> token) 269 | { 270 | if (token == "\\") 271 | { 272 | ssline.clear(); 273 | getline(is, sline); 274 | ssline.str(sline); 275 | } 276 | else 277 | { 278 | indices.push_back(std::stof(token)); 279 | } 280 | } 281 | parsed.surf = true; 282 | } 283 | else if (start == "parm") 284 | { 285 | ssline >> start; 286 | if (start == "u") 287 | { 288 | while (ssline >> token) 289 | { 290 | if (token == "\\") 291 | { 292 | ssline.clear(); 293 | std::getline(is, sline); 294 | ssline.str(sline); 295 | } 296 | else 297 | { 298 | temp_uknots.push_back(std::stof(token)); 299 | } 300 | } 301 | } 302 | else if (start == "v") 303 | { 304 | while (ssline >> token) 305 | { 306 | if (token == "\\") 307 | { 308 | ssline.clear(); 309 | std::getline(is, sline); 310 | ssline.str(sline); 311 | } 312 | else 313 | { 314 | temp_vknots.push_back(std::stof(token)); 315 | } 316 | } 317 | } 318 | parsed.parm = true; 319 | } 320 | else if (start == "end") 321 | { 322 | break; 323 | } 324 | ssline.clear(); 325 | } 326 | 327 | // Check if necessary data was available in stream 328 | if (!parsed.cstype) 329 | { 330 | throw std::runtime_error("'cstype bspline / cstype rat bspline' line missing in file"); 331 | } 332 | if (!parsed.deg) 333 | { 334 | throw std::runtime_error("'deg' line missing/incomplete in file"); 335 | } 336 | if (!parsed.surf) 337 | { 338 | throw std::runtime_error("'surf' line missing/incomplete in file"); 339 | } 340 | if (!parsed.parm) 341 | { 342 | throw std::runtime_error("'parm' line missing/incomplete in file"); 343 | } 344 | 345 | int num_knots_u = temp_uknots.size(); 346 | int num_knots_v = temp_vknots.size(); 347 | int num_cp_u = num_knots_u - deg_u - 1; 348 | int num_cp_v = num_knots_v - deg_v - 1; 349 | 350 | ctrlPts.resize(num_cp_u, num_cp_v); 351 | weights.resize(num_cp_u, num_cp_v); 352 | size_t num = 0; 353 | for (int j = 0; j < num_cp_v; ++j) 354 | { 355 | for (int i = 0; i < num_cp_u; ++i) 356 | { 357 | assert(i < ctrlPts.rows() && j < ctrlPts.cols()); 358 | ctrlPts(i, j) = ctrl_pts_buf[indices[num] - 1]; 359 | weights(i, j) = weights_buf[indices[num] - 1]; 360 | ++num; 361 | } 362 | } 363 | 364 | knots_u = temp_uknots; 365 | knots_v = temp_vknots; 366 | } 367 | 368 | /** 369 | * Save curve data to a Wavefront OBJ stream. 370 | * @param os The output stream 371 | * @param deg Degree of the curve 372 | * @param knots Knot vector of the curve 373 | * @param ctrlPts Array of control points 374 | * @param weights Array of corresponding weights 375 | * @param rational Whether rational 376 | */ 377 | template 378 | void curveSaveOBJ(std::ostream &os, unsigned int degree, const std::vector &knots, 379 | const std::vector> &ctrlPts, const std::vector &weights, 380 | bool rational) 381 | { 382 | using std::endl; 383 | 384 | for (int i = 0; i < ctrlPts.size(); ++i) 385 | { 386 | os << "v " << ctrlPts[i].x << " " << ctrlPts[i].y << " " << ctrlPts[i].z << " " 387 | << weights[i] << endl; 388 | } 389 | 390 | int n_knots = knots.size(); 391 | int n_cp = ctrlPts.size(); 392 | 393 | if (!rational) 394 | { 395 | os << "cstype bspline" << endl; 396 | } 397 | else 398 | { 399 | os << "cstype rat bspline" << endl; 400 | } 401 | os << "deg " << degree << endl << "curv "; 402 | os << knots[degree] << " " << knots[n_knots - degree - 1]; 403 | for (int i = 0; i < n_cp; ++i) 404 | { 405 | os << " " << i + 1; 406 | } 407 | os << endl << "parm u"; 408 | for (auto knot : knots) 409 | { 410 | os << " " << knot; 411 | } 412 | os << endl << "end"; 413 | } 414 | 415 | /** 416 | * Save surface data to a Wavefront OBJ stream. 417 | * @param os The output stream 418 | * @param deg_u Degree of the surface along u-direction 419 | * @param deg_v Degree of the surface along v-direction 420 | * @param knots_u Knot vector of the surface along u-direction 421 | * @param knots_v Knot vector of the surface along v-direction 422 | * @param ctrlPts 2D grid of control points 423 | * @param weights 2D grid of corresponding weights 424 | * @param rational Whether rational 425 | */ 426 | template 427 | void surfaceSaveOBJ(std::ostream &os, unsigned int deg_u, unsigned int deg_v, 428 | const std::vector &knots_u, const std::vector &knots_v, 429 | const array2> &ctrlPts, const array2 &weights, bool rational) 430 | { 431 | 432 | using std::endl; 433 | 434 | if (ctrlPts.rows() == 0 || ctrlPts.cols() == 0) 435 | { 436 | return; 437 | } 438 | 439 | for (int j = 0; j < ctrlPts.cols(); j++) 440 | { 441 | for (int i = 0; i < ctrlPts.rows(); i++) 442 | { 443 | os << "v " << ctrlPts(i, j).x << " " << ctrlPts(i, j).y << " " << ctrlPts(i, j).z 444 | << " " << weights(i, j) << endl; 445 | } 446 | } 447 | 448 | int nknots_u = knots_u.size(); 449 | int nknots_v = knots_v.size(); 450 | 451 | int nCpU = ctrlPts.rows(); 452 | int nCpV = ctrlPts.cols(); 453 | 454 | if (!rational) 455 | { 456 | os << "cstype bspline" << endl; 457 | } 458 | else 459 | { 460 | os << "cstype rat bspline" << endl; 461 | } 462 | os << "deg " << deg_u << " " << deg_v << endl << "surf "; 463 | os << knots_u[deg_u] << " " << knots_u[nknots_u - deg_u - 1] << " " << knots_v[deg_v] << " " 464 | << knots_v[nknots_v - deg_v - 1]; 465 | for (int i = 0; i < nCpU * nCpV; i++) 466 | { 467 | os << " " << i + 1; 468 | } 469 | os << endl << "parm u"; 470 | for (auto knot : knots_u) 471 | { 472 | os << " " << knot; 473 | } 474 | os << endl << "parm v"; 475 | for (auto knot : knots_v) 476 | { 477 | os << " " << knot; 478 | } 479 | os << endl << "end"; 480 | } 481 | 482 | } // namespace internal 483 | 484 | ///////////////////////////////////////////////////////////////////// 485 | 486 | /** 487 | * Read curve data from a Wavefront OBJ stream and populate a RationalCurve object 488 | * @param is The input stream 489 | * @return RationalCurve object 490 | */ 491 | template RationalCurve curveReadOBJ(std::istream &is) 492 | { 493 | RationalCurve crv; 494 | std::vector> control_points; 495 | bool rat; 496 | internal::curveReadOBJ(is, crv.degree, crv.knots, crv.control_points, crv.weights, rat); 497 | return crv; 498 | } 499 | 500 | /** 501 | * Read surface data from a Wavefront OBJ stream and populate a RationalSurface object 502 | * @param is The input stream 503 | * @return RationalSurface object 504 | */ 505 | template RationalSurface surfaceReadOBJ(std::istream &is) 506 | { 507 | RationalSurface srf; 508 | bool rat; 509 | internal::surfaceReadOBJ(is, srf.degree_u, srf.degree_v, srf.knots_u, srf.knots_v, 510 | srf.control_points, srf.weights, rat); 511 | return srf; 512 | } 513 | 514 | /** 515 | * Save curve data from a Wavefront OBJ stream and populate a RationalSurface object 516 | * @param os The output stream 517 | * @param Curve object to save 518 | */ 519 | template void curveSaveOBJ(std::ostream &os, const Curve &crv) 520 | { 521 | std::vector w(crv.control_points.size(), T(1)); 522 | internal::curveSaveOBJ(os, crv.degree, crv.knots, crv.control_points, w, false); 523 | } 524 | 525 | /** 526 | * Save rational curve data to a Wavefront OBJ stream. 527 | * @param os The output stream 528 | * @param RationalCurve object to save 529 | */ 530 | template void curveSaveOBJ(std::ostream &os, const RationalCurve &crv) 531 | { 532 | internal::curveSaveOBJ(os, crv.degree, crv.knots, crv.control_points, crv.weights, true); 533 | } 534 | 535 | /** 536 | * Save non-rational surface data to a Wavefront OBJ stream. 537 | * @param os The output stream 538 | * @param srf Surface object to save 539 | */ 540 | template void surfaceSaveOBJ(std::ostream &os, const Surface &srf) 541 | { 542 | array2 w(srf.control_points.rows(), srf.control_points.cols(), T(1)); 543 | internal::surfaceSaveOBJ(os, srf.degree_u, srf.degree_v, srf.knots_u, srf.knots_v, 544 | srf.control_points, w, false); 545 | } 546 | 547 | /** 548 | * Save rational surface data to a Wavefront OBJ stream. 549 | * @param os The output stream 550 | * @param srf RationalSurface object to save 551 | */ 552 | template 553 | void surfaceSaveOBJ(std::ostream &os, const RationalSurface &srf) 554 | { 555 | internal::surfaceSaveOBJ(os, srf.degree_u, srf.degree_v, srf.knots_u, srf.knots_v, 556 | srf.control_points, srf.weights, true); 557 | } 558 | 559 | ///////////////////////////////////////////////////////////////////// 560 | 561 | /** 562 | * Read curve data from a Wavefront OBJ file and populate a RationalCurve object 563 | * @param filename Name of the file 564 | * @return RationalCurve object 565 | */ 566 | template RationalCurve curveReadOBJ(const std::string &filename) 567 | { 568 | std::ifstream file(filename); 569 | if (!file) 570 | { 571 | throw std::runtime_error("File not found: " + filename); 572 | } 573 | 574 | return curveReadOBJ(file); 575 | } 576 | 577 | /** 578 | * Read surface data from a Wavefront OBJ file and populate a RationalSurface object 579 | * @param filename Name of the file 580 | * @return RationalSurface object 581 | */ 582 | template RationalSurface surfaceReadOBJ(const std::string &filename) 583 | { 584 | std::ifstream file(filename); 585 | if (!file) 586 | { 587 | throw std::runtime_error("File not found: " + filename); 588 | } 589 | 590 | return surfaceReadOBJ(file); 591 | } 592 | 593 | /** 594 | * Save curve data from a Wavefront OBJ file and populate a RationalSurface object 595 | * @param filename Name of the file 596 | * @param Curve object to save 597 | */ 598 | template void curveSaveOBJ(const std::string &filename, const Curve &crv) 599 | { 600 | std::ofstream fout(filename); 601 | curveSaveOBJ(fout, crv); 602 | } 603 | 604 | /** 605 | * Save rational curve data to a Wavefront OBJ file. 606 | * @param filename Name of the file 607 | * @param RationalCurve object to save 608 | */ 609 | template void curveSaveOBJ(const std::string &filename, const RationalCurve &crv) 610 | { 611 | std::ofstream fout(filename); 612 | curveSaveOBJ(fout, crv); 613 | } 614 | 615 | /** 616 | * Save non-rational surface data to a Wavefront OBJ file. 617 | * @param filename Name of the file 618 | * @param srf Surface object to save 619 | */ 620 | template void surfaceSaveOBJ(const std::string &filename, const Surface &srf) 621 | { 622 | std::ofstream fout(filename); 623 | surfaceSaveOBJ(fout, srf); 624 | } 625 | 626 | /** 627 | * Save rational surface data to a Wavefront OBJ file. 628 | * @param filename Name of the file 629 | * @param srf RationalSurface object to save 630 | */ 631 | template 632 | void surfaceSaveOBJ(const std::string &filename, const RationalSurface &srf) 633 | { 634 | std::ofstream fout(filename); 635 | surfaceSaveOBJ(fout, srf); 636 | } 637 | 638 | } // namespace tinynurbs 639 | 640 | #endif // TINYNURBS_OBJ_H 641 | -------------------------------------------------------------------------------- /include/tinynurbs/tinynurbs.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Import the entire library into the tinynurbs namespace. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be found in 5 | * the LICENSE file. 6 | */ 7 | 8 | #include "core/basis.h" 9 | #include "core/check.h" 10 | #include "core/curve.h" 11 | #include "core/evaluate.h" 12 | #include "core/modify.h" 13 | #include "core/surface.h" 14 | #include "io/obj.h" 15 | -------------------------------------------------------------------------------- /include/tinynurbs/util/array2.h: -------------------------------------------------------------------------------- 1 | /** 2 | * A simple class for 2D runtime arrays. Mainly used for control points and 3 | * weights of surfaces. 4 | * 5 | * Use of this source code is governed by a BSD-style license that can be found in 6 | * the LICENSE file. 7 | */ 8 | 9 | #ifndef TINYNURBS_ARRAY2_H 10 | #define TINYNURBS_ARRAY2_H 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | namespace tinynurbs 17 | { 18 | 19 | /** 20 | * A simple class for representing 2D runtime arrays. 21 | */ 22 | template class array2 23 | { 24 | public: 25 | array2() = default; 26 | array2(const array2 &arr) = default; 27 | array2 &operator=(const array2 &arr) = default; 28 | array2(array2 &&arr) = default; 29 | array2 &operator=(array2 &&arr) = default; 30 | array2(size_t rows, size_t cols, T default_value = T()) { resize(rows, cols, default_value); } 31 | array2(size_t rows, size_t cols, const std::vector &arr) 32 | : rows_(rows), cols_(cols), data_(arr) 33 | { 34 | if (arr.size() != rows * cols) 35 | { 36 | throw std::runtime_error("Dimensions do not match with size of vector"); 37 | } 38 | } 39 | void resize(size_t rows, size_t cols, T val = T()) 40 | { 41 | data_.resize(rows * cols, val); 42 | rows_ = rows; 43 | cols_ = cols; 44 | } 45 | void clear() 46 | { 47 | rows_ = cols_ = 0; 48 | data_.clear(); 49 | } 50 | T operator()(size_t row, size_t col) const 51 | { 52 | assert(row < rows_ && col < cols_); 53 | return data_[row * cols_ + col]; 54 | } 55 | T &operator()(size_t row, size_t col) 56 | { 57 | assert(row < rows_ && col < cols_); 58 | return data_[row * cols_ + col]; 59 | } 60 | T operator[](size_t idx) const 61 | { 62 | assert(idx < data_.size()); 63 | return data_[idx]; 64 | } 65 | T &operator[](size_t idx) 66 | { 67 | assert(idx < data_.size()); 68 | return data_[idx]; 69 | } 70 | size_t rows() const { return rows_; } 71 | size_t cols() const { return cols_; } 72 | size_t size() const { return data_.size(); } 73 | 74 | private: 75 | size_t rows_, cols_; 76 | std::vector data_; 77 | }; 78 | 79 | } // namespace tinynurbs 80 | 81 | #endif // TINYNURBS_ARRAY2_H 82 | -------------------------------------------------------------------------------- /include/tinynurbs/util/util.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper functions 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be found in 5 | * the LICENSE file. 6 | */ 7 | 8 | #ifndef TINYNURBS_UTIL 9 | #define TINYNURBS_UTIL 10 | 11 | #include "array2.h" 12 | #include 13 | #include 14 | 15 | namespace tinynurbs 16 | { 17 | namespace util 18 | { 19 | 20 | /** 21 | * Convert an nd point in homogenous coordinates to an (n-1)d point in cartesian 22 | * coordinates by perspective division 23 | * @param[in] pt Point in homogenous coordinates 24 | * @return Point in cartesian coordinates 25 | */ 26 | template 27 | inline glm::vec homogenousToCartesian(const glm::vec &pt) 28 | { 29 | return glm::vec(pt / pt[pt.length() - 1]); 30 | } 31 | 32 | /** 33 | * Convert a list of nd points in homogenous coordinates to a list of (n-1)d points in cartesian 34 | * coordinates by perspective division 35 | * @param[in] ptsws Points in homogenous coordinates 36 | * @param[out] pts Points in cartesian coordinates 37 | * @param[out] ws Homogenous weights 38 | */ 39 | template 40 | inline void homogenousToCartesian(const std::vector> &ptsws, 41 | std::vector> &pts, std::vector &ws) 42 | { 43 | pts.clear(); 44 | ws.clear(); 45 | pts.reserve(ptsws.size()); 46 | ws.reserve(ptsws.size()); 47 | for (int i = 0; i < ptsws.size(); ++i) 48 | { 49 | const glm::vec &ptw_i = ptsws[i]; 50 | pts.push_back(glm::vec(ptw_i / ptw_i[ptw_i.length() - 1])); 51 | ws.push_back(ptw_i[ptw_i.length() - 1]); 52 | } 53 | } 54 | 55 | /** 56 | * Convert a 2D list of nd points in homogenous coordinates to cartesian 57 | * coordinates by perspective division 58 | * @param[in] ptsws Points in homogenous coordinates 59 | * @param[out] pts Points in cartesian coordinates 60 | * @param[out] ws Homogenous weights 61 | */ 62 | template 63 | inline void homogenousToCartesian(const array2> &ptsws, 64 | array2> &pts, array2 &ws) 65 | { 66 | pts.resize(ptsws.rows(), ptsws.cols()); 67 | ws.resize(ptsws.rows(), ptsws.cols()); 68 | for (int i = 0; i < ptsws.rows(); ++i) 69 | { 70 | for (int j = 0; j < ptsws.cols(); ++j) 71 | { 72 | const glm::vec &ptw_ij = ptsws(i, j); 73 | T w_ij = ptw_ij[nd - 1]; 74 | pts(i, j) = glm::vec(ptw_ij / w_ij); 75 | ws(i, j) = w_ij; 76 | } 77 | } 78 | } 79 | 80 | /** 81 | * Convert an nd point in cartesian coordinates to an (n+1)d point in homogenous 82 | * coordinates 83 | * @param[in] pt Point in cartesian coordinates 84 | * @param[in] w Weight 85 | * @return Input point in homogenous coordinates 86 | */ 87 | template 88 | inline glm::vec cartesianToHomogenous(const glm::vec &pt, T w) 89 | { 90 | return glm::vec(pt * w, w); 91 | } 92 | 93 | /** 94 | * Convert list of points in cartesian coordinates to homogenous coordinates 95 | * @param[in] pts Points in cartesian coordinates 96 | * @param[in] ws Weights 97 | * @return Points in homogenous coordinates 98 | */ 99 | template 100 | inline std::vector> 101 | cartesianToHomogenous(const std::vector> &pts, const std::vector &ws) 102 | { 103 | std::vector> Cw; 104 | Cw.reserve(pts.size()); 105 | for (int i = 0; i < pts.size(); ++i) 106 | { 107 | Cw.push_back(cartesianToHomogenous(pts[i], ws[i])); 108 | } 109 | return Cw; 110 | } 111 | 112 | /** 113 | * Convert 2D list of points in cartesian coordinates to homogenous coordinates 114 | * @param[in] pts Points in cartesian coordinates 115 | * @param[in] ws Weights 116 | * @return Points in homogenous coordinates 117 | */ 118 | template 119 | inline array2> cartesianToHomogenous(const array2> &pts, 120 | const array2 &ws) 121 | { 122 | array2> Cw(pts.rows(), pts.cols()); 123 | for (int i = 0; i < pts.rows(); ++i) 124 | { 125 | for (int j = 0; j < pts.cols(); ++j) 126 | { 127 | Cw(i, j) = util::cartesianToHomogenous(pts(i, j), ws(i, j)); 128 | } 129 | } 130 | return Cw; 131 | } 132 | 133 | /** 134 | * Convert an (n+1)d point to an nd point without perspective division 135 | * by truncating the last dimension 136 | * @param[in] pt Point in homogenous coordinates 137 | * @return Input point in cartesian coordinates 138 | */ 139 | template 140 | inline glm::vec truncateHomogenous(const glm::vec &pt) 141 | { 142 | return glm::vec(pt); 143 | } 144 | 145 | /** 146 | * Compute the binomial coefficient (nCk) using the formula 147 | * \product_{i=0}^k (n + 1 - i) / i 148 | */ 149 | inline unsigned int binomial(unsigned int n, unsigned int k) 150 | { 151 | unsigned int result = 1; 152 | if (k > n) 153 | { 154 | return 0; 155 | } 156 | for (unsigned int i = 1; i <= k; ++i) 157 | { 158 | result *= (n + 1 - i); 159 | result /= i; 160 | } 161 | return result; 162 | } 163 | 164 | /** 165 | * Check if two numbers are close enough within eps 166 | * @param[in] a First number 167 | * @param[in] b Second number 168 | * @param[in] eps Tolerance for checking closeness 169 | * @return Whether the numbers are close w.r.t. the tolerance 170 | */ 171 | template inline bool close(T a, T b, double eps = std::numeric_limits::epsilon()) 172 | { 173 | return (std::abs(a - b) < eps) ? true : false; 174 | } 175 | 176 | /** 177 | * Map numbers from one interval to another 178 | * @param[in] val Number to map to another range 179 | * @param[in] old_min Minimum value of original range 180 | * @param[in] old_max Maximum value of original range 181 | * @param[in] new_min Minimum value of new range 182 | * @param[in] new_max Maximum value of new range 183 | * @return Number mapped to new range 184 | */ 185 | template inline T mapToRange(T val, T old_min, T old_max, T new_min, T new_max) 186 | { 187 | T old_range = old_max - old_min; 188 | T new_range = new_max - new_min; 189 | return (((val - old_min) * new_range) / old_range) + new_min; 190 | } 191 | 192 | } // namespace util 193 | 194 | } // namespace tinynurbs 195 | 196 | #endif // TINYNURBS_UTIL 197 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(unittests catch.hpp test_main.cpp 2 | test_curve.cpp test_rational_curve.cpp 3 | test_surface.cpp test_rational_surface.cpp) 4 | target_include_directories(unittests PRIVATE ${GLM_INCLUDE_DIRS}) 5 | target_link_libraries(unittests PUBLIC tinynurbs::tinynurbs) 6 | add_test(NAME unittests COMMAND unittests) 7 | -------------------------------------------------------------------------------- /tests/test_curve.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "catch.hpp" 7 | 8 | using namespace std; 9 | 10 | tinynurbs::Curve3f getNonrationalBezierCurve() { 11 | tinynurbs::Curve3f crv; 12 | crv.control_points = {glm::vec3(-1, 0, 0), 13 | glm::vec3(0, 1, 0), 14 | glm::vec3(1, 0, 0) 15 | }; 16 | crv.knots = {0, 0, 0, 1, 1, 1}; 17 | crv.degree = 2; 18 | return crv; 19 | } 20 | 21 | TEST_CASE("curvePoint (non-rational)", "[curve, non-rational, evaluate]") 22 | { 23 | auto crv = getNonrationalBezierCurve(); 24 | glm::vec2 pt1 = tinynurbs::curvePoint(crv, 0.f); 25 | REQUIRE(pt1.x == Approx(-1)); 26 | REQUIRE(pt1.y == Approx(0)); 27 | glm::vec2 pt2 = tinynurbs::curvePoint(crv, 1.f); 28 | REQUIRE(pt2.x == Approx(1)); 29 | REQUIRE(pt2.y == Approx(0)); 30 | } 31 | 32 | TEST_CASE("curveTangent (glm::vec2)", "[curve, non-rational, evaluate]") 33 | { 34 | auto crv = getNonrationalBezierCurve(); 35 | glm::vec2 tgt1 = tinynurbs::curveTangent(crv, 0.5f); 36 | REQUIRE(tgt1.x == Approx(1)); 37 | REQUIRE(tgt1.y == Approx(0)); 38 | } 39 | 40 | TEST_CASE("curveIsValid (non-rational)", "[curve, non-rational, check]") 41 | { 42 | auto crv = getNonrationalBezierCurve(); 43 | bool is_valid = tinynurbs::curveIsValid(crv); 44 | REQUIRE(is_valid == true); 45 | 46 | crv = getNonrationalBezierCurve(); 47 | crv.degree = 4; 48 | is_valid = tinynurbs::curveIsValid(crv); 49 | REQUIRE(is_valid == false); 50 | } 51 | 52 | TEST_CASE("curveKnotMultiplicity (non-rational)", "[knots, check]") 53 | { 54 | { 55 | const std::vector knots{0.0, 0.0, 0.0, 1.0, 1.0}; 56 | unsigned int knotMult0 = tinynurbs::knotMultiplicity(knots, 0.0); 57 | REQUIRE(knotMult0 == 3); 58 | unsigned int knotMult1 = tinynurbs::knotMultiplicity(knots, 1.0); 59 | REQUIRE(knotMult1 == 2); 60 | } 61 | { 62 | const std::vector knots{0.0, 1.0}; 63 | unsigned int knotMult0 = tinynurbs::knotMultiplicity(knots, 0.0); 64 | REQUIRE(knotMult0 == 1); 65 | unsigned int knotMult1 = tinynurbs::knotMultiplicity(knots, 1.0); 66 | REQUIRE(knotMult1 == 1); 67 | } 68 | { 69 | const std::vector knots{0.0, 0.0001, 0.0002, 1.0, 1.0001}; 70 | unsigned int knotMult0 = tinynurbs::knotMultiplicity(knots, 0.0); 71 | REQUIRE(knotMult0 == 1); 72 | unsigned int knotMult1 = tinynurbs::knotMultiplicity(knots, 1.0); 73 | REQUIRE(knotMult1 == 1); 74 | } 75 | } 76 | 77 | TEST_CASE("curveInsertKnot (non-rational)", "[curve, non-rational, modify]") 78 | { 79 | auto crv = getNonrationalBezierCurve(); 80 | glm::vec2 pt = tinynurbs::curvePoint(crv, 0.25f); 81 | 82 | size_t n_knots_prev = crv.knots.size(); 83 | size_t n_control_points_prev = crv.control_points.size(); 84 | 85 | auto new_crv = tinynurbs::curveKnotInsert(crv, 0.25f, 1); 86 | glm::vec2 new_pt = tinynurbs::curvePoint(new_crv, 0.25f); 87 | size_t n_knots_curr = new_crv.knots.size(); 88 | size_t n_control_points_curr = new_crv.control_points.size(); 89 | 90 | REQUIRE((n_knots_prev + 1) == n_knots_curr); 91 | REQUIRE((n_control_points_prev + 1) == n_control_points_curr); 92 | REQUIRE(pt.x == Approx(new_pt.x)); 93 | REQUIRE(pt.y == Approx(new_pt.y)); 94 | } 95 | 96 | TEST_CASE("curveSplit (non-rational)", "[curve, non-rational, modify]") 97 | { 98 | auto crv = getNonrationalBezierCurve(); 99 | float u = 0.5f; 100 | tinynurbs::Curve3f left, right; 101 | std::tie(left, right) = tinynurbs::curveSplit(crv, u); 102 | 103 | bool is_valid_l = tinynurbs::curveIsValid(left); 104 | bool is_valid_r = tinynurbs::curveIsValid(right); 105 | 106 | REQUIRE(is_valid_l == true); 107 | REQUIRE(is_valid_r == true); 108 | 109 | REQUIRE(left.degree == crv.degree); 110 | REQUIRE(right.degree == crv.degree); 111 | 112 | for (unsigned int i = 0; i < left.degree + 1; ++i) { 113 | int d = static_cast(left.knots.size()) - (left.degree + 1); 114 | REQUIRE(left.knots[d+i] == Approx(u)); 115 | } 116 | 117 | for (unsigned int i = 0; i < right.degree + 1; ++i) { 118 | REQUIRE(right.knots[i] == Approx(u)); 119 | } 120 | 121 | glm::vec2 pt1 = tinynurbs::curvePoint(crv, left.knots[left.knots.size() - 1]); 122 | glm::vec2 pt2 = tinynurbs::curvePoint(crv, right.knots[0]); 123 | REQUIRE(pt1.x == Approx(pt2.x)); 124 | REQUIRE(pt1.y == Approx(pt2.y)); 125 | } 126 | 127 | TEST_CASE("curveReadOBJ and curveSaveOBJ (non-rational)", "[curve, non-rational, obj]") 128 | { 129 | auto crv = getNonrationalBezierCurve(); 130 | tinynurbs::curveSaveOBJ("curve.obj", crv); 131 | auto read_crv = tinynurbs::Curve3f(tinynurbs::curveReadOBJ("curve.obj")); 132 | REQUIRE(crv.degree == read_crv.degree); 133 | REQUIRE(crv.knots.size() == read_crv.knots.size()); 134 | for (int i = 0; i < crv.knots.size(); ++i) { 135 | REQUIRE(crv.knots[i] == Approx(read_crv.knots[i])); 136 | } 137 | REQUIRE(crv.control_points.size() == read_crv.control_points.size()); 138 | for (int i = 0; i < crv.control_points.size(); ++i) { 139 | REQUIRE(crv.control_points[i].x == Approx(read_crv.control_points[i].x)); 140 | REQUIRE(crv.control_points[i].y == Approx(read_crv.control_points[i].y)); 141 | } 142 | } -------------------------------------------------------------------------------- /tests/test_main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include "catch.hpp" -------------------------------------------------------------------------------- /tests/test_rational_curve.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "catch.hpp" 7 | 8 | using namespace std; 9 | 10 | // Unit circle 11 | tinynurbs::RationalCurve3f getCircle() { 12 | tinynurbs::RationalCurve3f crv; 13 | crv.control_points = {glm::vec3(1, 0, 0), 14 | glm::vec3(1, 1, 0), 15 | glm::vec3(0, 1, 0), 16 | glm::vec3(-1, 1, 0), 17 | glm::vec3(-1, 0, 0), 18 | glm::vec3(-1, -1, 0), 19 | glm::vec3(0, -1, 0), 20 | glm::vec3(1, -1, 0), 21 | glm::vec3(1, 0, 0) 22 | }; 23 | const float sqrt2_over_2 = std::sqrt(2.f) / 2.f; 24 | crv.weights = {1, sqrt2_over_2, 1, sqrt2_over_2, 1, 25 | sqrt2_over_2, 1, sqrt2_over_2, 1 26 | }; 27 | crv.knots = {0, 0, 0, 28 | glm::half_pi(), glm::half_pi(), 29 | glm::pi(), glm::pi(), 30 | 3 * glm::half_pi(), 3 * glm::half_pi(), 31 | glm::two_pi(), glm::two_pi(), glm::two_pi() 32 | }; 33 | crv.degree = 2; 34 | return crv; 35 | } 36 | 37 | TEST_CASE("curvePoint (rational)", "[curve, rational, evaluate]") 38 | { 39 | auto crv = getCircle(); 40 | glm::vec3 pt1 = tinynurbs::curvePoint(crv, 0.f); 41 | REQUIRE(pt1.x == Approx(1)); 42 | REQUIRE(pt1.y == Approx(0)); 43 | REQUIRE(pt1.z == Approx(0)); 44 | glm::vec3 pt2 = curvePoint(crv, glm::pi()); 45 | REQUIRE(pt2.x == Approx(-1)); 46 | REQUIRE(pt2.y == Approx(0)); 47 | REQUIRE(pt2.z == Approx(0)); 48 | } 49 | 50 | TEST_CASE("curveTangent (rational)", "[curve, rational, evaluate]") 51 | { 52 | auto crv = getCircle(); 53 | glm::vec3 tgt1 = tinynurbs::curveTangent(crv, 0.f); 54 | REQUIRE(tgt1.x == Approx(0)); 55 | REQUIRE(tgt1.y == Approx(1)); 56 | REQUIRE(tgt1.z == Approx(0)); 57 | glm::vec3 tgt2 = tinynurbs::curveTangent(crv, glm::pi()); 58 | REQUIRE(tgt2.x == Approx(0)); 59 | REQUIRE(tgt2.y == Approx(-1)); 60 | REQUIRE(tgt2.z == Approx(0)); 61 | } 62 | 63 | TEST_CASE("curveIsValid (rational)", "[curve, rational, check]") 64 | { 65 | auto crv = getCircle(); 66 | bool is_valid = tinynurbs::curveIsValid(crv); 67 | REQUIRE(is_valid == true); 68 | 69 | crv = getCircle(); 70 | crv.degree = 4; 71 | is_valid = tinynurbs::curveIsValid(crv); 72 | REQUIRE(is_valid == false); 73 | } 74 | 75 | TEST_CASE("curveInsertKnot (rational)", "[curve, rational, modify]") 76 | { 77 | auto crv = getCircle(); 78 | glm::vec3 pt = tinynurbs::curvePoint(crv, 0.25f); 79 | 80 | size_t n_knots_prev = crv.knots.size(); 81 | size_t n_control_points_prev = crv.control_points.size(); 82 | 83 | auto new_crv = tinynurbs::curveKnotInsert(crv, 0.25f, 1); 84 | glm::vec3 new_pt = tinynurbs::curvePoint(new_crv, 0.25f); 85 | size_t n_knots_curr = new_crv.knots.size(); 86 | size_t n_control_points_curr = new_crv.control_points.size(); 87 | 88 | REQUIRE((n_knots_prev + 1) == n_knots_curr); 89 | REQUIRE((n_control_points_prev + 1) == n_control_points_curr); 90 | REQUIRE(pt.x == Approx(new_pt.x)); 91 | REQUIRE(pt.y == Approx(new_pt.y)); 92 | REQUIRE(pt.z == Approx(new_pt.z)); 93 | } 94 | 95 | TEST_CASE("curveSplit (rational)", "[curve, rational, modify]") 96 | { 97 | auto crv = getCircle(); 98 | float u = glm::pi(); 99 | tinynurbs::RationalCurve3f left, right; 100 | std::tie(left, right) = tinynurbs::curveSplit(crv, u); 101 | 102 | bool is_valid_l = tinynurbs::curveIsValid(left); 103 | bool is_valid_r = tinynurbs::curveIsValid(right); 104 | 105 | REQUIRE(is_valid_l == true); 106 | REQUIRE(is_valid_r == true); 107 | 108 | REQUIRE(left.degree == crv.degree); 109 | REQUIRE(right.degree == crv.degree); 110 | 111 | for (int i = 0; i < left.degree + 1; ++i) { 112 | int d = left.knots.size() - (left.degree + 1); 113 | REQUIRE(left.knots[d+i] == Approx(u)); 114 | } 115 | 116 | for (int i = 0; i < right.degree + 1; ++i) { 117 | REQUIRE(right.knots[i] == Approx(u)); 118 | } 119 | 120 | glm::vec3 pt1 = tinynurbs::curvePoint(crv, left.knots[left.knots.size() - 1]); 121 | glm::vec3 pt2 = tinynurbs::curvePoint(crv, right.knots[0]); 122 | REQUIRE(pt1.x == Approx(pt2.x)); 123 | REQUIRE(pt1.y == Approx(pt2.y)); 124 | REQUIRE(pt1.z == Approx(pt2.z)); 125 | } 126 | 127 | TEST_CASE("curveReadOBJ and curveSaveOBJ (rational)", "[curve, rational, obj]") 128 | { 129 | auto crv = getCircle(); 130 | tinynurbs::curveSaveOBJ("curve.obj", crv); 131 | auto read_crv = tinynurbs::curveReadOBJ("curve.obj"); 132 | REQUIRE(crv.degree == read_crv.degree); 133 | REQUIRE(crv.knots.size() == read_crv.knots.size()); 134 | for (int i = 0; i < crv.knots.size(); ++i) { 135 | REQUIRE(crv.knots[i] == Approx(read_crv.knots[i])); 136 | } 137 | REQUIRE(crv.control_points.size() == read_crv.control_points.size()); 138 | for (int i = 0; i < crv.control_points.size(); ++i) { 139 | REQUIRE(crv.control_points[i].x == Approx(read_crv.control_points[i].x)); 140 | REQUIRE(crv.control_points[i].y == Approx(read_crv.control_points[i].y)); 141 | REQUIRE(crv.control_points[i].z == Approx(read_crv.control_points[i].z)); 142 | } 143 | REQUIRE(crv.weights.size() == read_crv.weights.size()); 144 | for (int i = 0; i < crv.weights.size(); ++i) { 145 | REQUIRE(crv.weights[i] == Approx(read_crv.weights[i])); 146 | } 147 | } -------------------------------------------------------------------------------- /tests/test_rational_surface.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "catch.hpp" 7 | 8 | using namespace std; 9 | 10 | tinynurbs::RationalSurface3f getHemisphere() { 11 | tinynurbs::RationalSurface3f srf; 12 | srf.degree_u = 3; 13 | srf.degree_v = 3; 14 | srf.knots_u = {0, 0, 0, 0, 1, 1, 1, 1}; 15 | srf.knots_v = {0, 0, 0, 0, 1, 1, 1, 1}; 16 | // 4x4 grid (tinynurbs::array2) of control points and weights 17 | // https://www.geometrictools.com/Documentation/NURBSCircleSphere.pdf 18 | srf.control_points = {4, 4, 19 | {glm::vec3(0, 0, 1), glm::vec3(0, 0, 1), glm::vec3(0, 0, 1), glm::vec3(0, 0, 1), 20 | glm::vec3(2, 0, 1), glm::vec3(2, 4, 1), glm::vec3(-2, 4, 1), glm::vec3(-2, 0, 1), 21 | glm::vec3(2, 0, -1), glm::vec3(2, 4, -1), glm::vec3(-2, 4, -1), glm::vec3(-2, 0, -1), 22 | glm::vec3(0, 0, -1), glm::vec3(0, 0, -1), glm::vec3(0, 0, -1), glm::vec3(0, 0, -1) 23 | } 24 | }; 25 | srf.weights = {4, 4, 26 | {1, 1.f/3.f, 1.f/3.f, 1, 27 | 1.f/3.f, 1.f/9.f, 1.f/9.f, 1.f/3.f, 28 | 1.f/3.f, 1.f/9.f, 1.f/9.f, 1.f/3.f, 29 | 1, 1.f/3.f, 1.f/3.f, 1 30 | } 31 | }; 32 | return srf; 33 | } 34 | 35 | TEST_CASE("surfacePoint (rational)", "[surface, rational, evaluate]") 36 | { 37 | auto srf = getHemisphere(); 38 | glm::vec3 pt1 = tinynurbs::surfacePoint(srf, 0.f, 0.f); 39 | glm::vec3 pt2 = tinynurbs::surfacePoint(srf, 1.f, 1.f); 40 | REQUIRE(pt1.x == Approx(0)); 41 | REQUIRE(pt1.y == Approx(0)); 42 | REQUIRE(pt1.z == Approx(1)); 43 | REQUIRE(pt2.x == Approx(0)); 44 | REQUIRE(pt2.y == Approx(0)); 45 | REQUIRE(pt2.z == Approx(-1)); 46 | } 47 | 48 | TEST_CASE("surfaceTangent (rational)", "[surface, rational, evaluate]") 49 | { 50 | auto srf = getHemisphere(); 51 | glm::vec3 tgt_u, tgt_v; 52 | std::tie(tgt_u, tgt_v) = tinynurbs::surfaceTangent(srf, 0.f, 0.f); 53 | REQUIRE(glm::length(tgt_u) == Approx(1)); 54 | REQUIRE(tgt_u.x == Approx(1)); 55 | REQUIRE(tgt_u.y == Approx(0)); 56 | REQUIRE(tgt_u.z == Approx(0)); 57 | // tgt_v should be a zero vector since the pole of the hemisphere is pinched 58 | REQUIRE(glm::length(tgt_v) == Approx(0)); 59 | REQUIRE(tgt_v.x == Approx(0)); 60 | REQUIRE(tgt_v.y == Approx(0)); 61 | REQUIRE(tgt_v.z == Approx(0)); 62 | } 63 | 64 | 65 | TEST_CASE("surfaceNormal (rational)", "[surface, rational, evaluate]") 66 | { 67 | auto srf = getHemisphere(); 68 | glm::vec3 n = tinynurbs::surfaceNormal(srf, 0.5f, 0.5f); 69 | REQUIRE(glm::length(n) == Approx(1)); 70 | REQUIRE(n.x == Approx(0)); 71 | REQUIRE(n.y == Approx(-1)); 72 | REQUIRE(n.z == Approx(0)); 73 | 74 | // Normal is zero vector since the pole is pinched 75 | n = tinynurbs::surfaceNormal(srf, 0.f, 0.f); 76 | REQUIRE(glm::length(n) == Approx(0)); 77 | REQUIRE(n.x == Approx(0)); 78 | REQUIRE(n.y == Approx(0)); 79 | REQUIRE(n.z == Approx(0)); 80 | } 81 | 82 | TEST_CASE("surfaceIsValid (rational)", "[surface, rational, check]") 83 | { 84 | auto srf = getHemisphere(); 85 | bool is_valid = tinynurbs::surfaceIsValid(srf); 86 | REQUIRE(is_valid == true); 87 | 88 | srf = getHemisphere(); 89 | srf.degree_u = 5; 90 | is_valid = tinynurbs::surfaceIsValid(srf); 91 | REQUIRE(is_valid == false); 92 | 93 | srf = getHemisphere(); 94 | srf.degree_v = 4; 95 | is_valid = tinynurbs::surfaceIsValid(srf); 96 | REQUIRE(is_valid == false); 97 | } 98 | 99 | TEST_CASE("surfaceInsertKnotU (rational)", "[surface, rational, modify]") 100 | { 101 | auto srf = getHemisphere(); 102 | unsigned int repeat = 2; 103 | 104 | glm::vec3 pt = tinynurbs::surfacePoint(srf, 0.25f, 0.5f); 105 | 106 | size_t n_knots_prev = srf.knots_u.size(); 107 | size_t n_control_points_prev = srf.control_points.rows(); 108 | 109 | auto new_srf = tinynurbs::surfaceKnotInsertU(srf, 0.25f, repeat); 110 | glm::vec3 new_pt = tinynurbs::surfacePoint(new_srf, 0.25f, 0.5f); 111 | 112 | size_t n_knots_curr = new_srf.knots_u.size(); 113 | size_t n_control_points_curr = new_srf.control_points.rows(); 114 | 115 | REQUIRE((n_knots_prev + repeat) == n_knots_curr); 116 | REQUIRE((n_control_points_prev + repeat) == n_control_points_curr); 117 | REQUIRE(pt.x == Approx(new_pt.x)); 118 | REQUIRE(pt.y == Approx(new_pt.y)); 119 | REQUIRE(pt.z == Approx(new_pt.z)); 120 | } 121 | 122 | TEST_CASE("surfaceInsertKnotV (rational)", "[surface, rational, modify]") 123 | { 124 | auto srf = getHemisphere(); 125 | unsigned int repeat = 2; 126 | 127 | glm::vec3 pt = tinynurbs::surfacePoint(srf, 0.25f, 0.5f); 128 | 129 | size_t n_knots_prev = srf.knots_v.size(); 130 | size_t n_control_points_prev = srf.control_points.cols(); 131 | 132 | auto new_srf = tinynurbs::surfaceKnotInsertV(srf, 0.5f, repeat); 133 | glm::vec3 new_pt = tinynurbs::surfacePoint(new_srf, 0.25f, 0.5f); 134 | 135 | size_t n_knots_curr = new_srf.knots_v.size(); 136 | size_t n_control_points_curr = new_srf.control_points.cols(); 137 | 138 | REQUIRE((n_knots_prev + repeat) == n_knots_curr); 139 | REQUIRE((n_control_points_prev + repeat) == n_control_points_curr); 140 | REQUIRE(pt.x == Approx(new_pt.x)); 141 | REQUIRE(pt.y == Approx(new_pt.y)); 142 | REQUIRE(pt.z == Approx(new_pt.z)); 143 | } 144 | 145 | TEST_CASE("surfaceSplitU (rational)", "[surface, rational, modify]") 146 | { 147 | auto srf = getHemisphere(); 148 | float u = 0.25f; 149 | tinynurbs::RationalSurface3f left, right; 150 | std::tie(left, right) = tinynurbs::surfaceSplitU(srf, u); 151 | 152 | bool is_valid_l = tinynurbs::surfaceIsValid(left); 153 | bool is_valid_r = tinynurbs::surfaceIsValid(right); 154 | 155 | REQUIRE(is_valid_l == true); 156 | REQUIRE(is_valid_r == true); 157 | 158 | REQUIRE(left.degree_u == srf.degree_u); 159 | REQUIRE(right.degree_u == srf.degree_u); 160 | REQUIRE(left.degree_v == srf.degree_v); 161 | REQUIRE(right.degree_v == srf.degree_v); 162 | 163 | for (int i = 0; i < left.degree_u + 1; ++i) { 164 | int d = left.knots_u.size() - (left.degree_u + 1); 165 | REQUIRE(left.knots_u[d+i] == Approx(u)); 166 | } 167 | 168 | for (int i = 0; i < right.degree_u + 1; ++i) { 169 | REQUIRE(right.knots_u[i] == Approx(u)); 170 | } 171 | 172 | tinynurbs::surfaceSaveOBJ("left_rational_u.obj", left); 173 | tinynurbs::surfaceSaveOBJ("right_rational_u.obj", right); 174 | 175 | glm::vec3 pt1 = tinynurbs::surfacePoint(srf, left.knots_u[left.knots_u.size() - 1], 0.f); 176 | glm::vec3 pt2 = tinynurbs::surfacePoint(srf, right.knots_u[0], 0.f); 177 | REQUIRE(pt1.x == Approx(pt2.x)); 178 | REQUIRE(pt1.y == Approx(pt2.y)); 179 | REQUIRE(pt1.z == Approx(pt2.z)); 180 | } 181 | 182 | TEST_CASE("surfaceSplitV (rational)", "[surface, rational, modify]") 183 | { 184 | auto srf = getHemisphere(); 185 | float v = 0.25f; 186 | tinynurbs::RationalSurface3f left, right; 187 | std::tie(left, right) = tinynurbs::surfaceSplitV(srf, v); 188 | 189 | bool is_valid_l = tinynurbs::surfaceIsValid(left); 190 | bool is_valid_r = tinynurbs::surfaceIsValid(right); 191 | 192 | REQUIRE(is_valid_l == true); 193 | REQUIRE(is_valid_r == true); 194 | 195 | REQUIRE(left.degree_u == srf.degree_u); 196 | REQUIRE(right.degree_u == srf.degree_u); 197 | REQUIRE(left.degree_v == srf.degree_v); 198 | REQUIRE(right.degree_v == srf.degree_v); 199 | 200 | for (int i = 0; i < left.degree_v + 1; ++i) { 201 | int d = left.knots_v.size() - (left.degree_v + 1); 202 | REQUIRE(left.knots_v[d+i] == Approx(v)); 203 | } 204 | 205 | for (int i = 0; i < right.degree_v + 1; ++i) { 206 | REQUIRE(right.knots_v[i] == Approx(v)); 207 | } 208 | 209 | tinynurbs::surfaceSaveOBJ("left_rational_v.obj", left); 210 | tinynurbs::surfaceSaveOBJ("right_rational_v.obj", right); 211 | 212 | glm::vec3 pt1 = tinynurbs::surfacePoint(srf, left.knots_v[left.knots_v.size() - 1], 0.f); 213 | glm::vec3 pt2 = tinynurbs::surfacePoint(srf, right.knots_v[0], 0.f); 214 | REQUIRE(pt1.x == Approx(pt2.x)); 215 | REQUIRE(pt1.y == Approx(pt2.y)); 216 | REQUIRE(pt1.z == Approx(pt2.z)); 217 | } 218 | 219 | TEST_CASE("surfaceReadOBJ and surfaceSaveOBJ (rational)", "[surface, obj]") 220 | { 221 | auto srf = getHemisphere(); 222 | 223 | tinynurbs::surfaceSaveOBJ("surface_rational.obj", srf); 224 | auto read_srf = tinynurbs::surfaceReadOBJ("surface_rational.obj"); 225 | 226 | REQUIRE(srf.degree_u == read_srf.degree_u); 227 | REQUIRE(srf.degree_v == read_srf.degree_v); 228 | REQUIRE(srf.knots_u.size() == read_srf.knots_u.size()); 229 | for (int i = 0; i < srf.knots_u.size(); ++i) { 230 | REQUIRE(srf.knots_u[i] == Approx(read_srf.knots_u[i])); 231 | } 232 | REQUIRE(srf.knots_v.size() == read_srf.knots_v.size()); 233 | for (int i = 0; i < srf.knots_v.size(); ++i) { 234 | REQUIRE(srf.knots_v[i] == Approx(read_srf.knots_v[i])); 235 | } 236 | REQUIRE(srf.control_points.rows() == read_srf.control_points.rows()); 237 | REQUIRE(srf.control_points.cols() == read_srf.control_points.cols()); 238 | for (int i = 0; i < srf.control_points.rows(); ++i) { 239 | for (int j = 0; j < srf.control_points.cols(); ++j) { 240 | REQUIRE(srf.control_points(i, j).x == Approx(read_srf.control_points(i, j).x)); 241 | REQUIRE(srf.control_points(i, j).y == Approx(read_srf.control_points(i, j).y)); 242 | REQUIRE(srf.control_points(i, j).z == Approx(read_srf.control_points(i, j).z)); 243 | } 244 | } 245 | REQUIRE(srf.weights.rows() == read_srf.weights.rows()); 246 | REQUIRE(srf.weights.cols() == read_srf.weights.cols()); 247 | for (int i = 0; i < srf.weights.rows(); ++i) { 248 | for (int j = 0; j < srf.weights.cols(); ++j) { 249 | REQUIRE(srf.weights(i, j) == Approx(read_srf.weights(i, j))); 250 | } 251 | } 252 | } -------------------------------------------------------------------------------- /tests/test_surface.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "catch.hpp" 6 | 7 | using namespace std; 8 | 9 | tinynurbs::Surface3f getBilinearPatch() { 10 | tinynurbs::Surface3f srf; 11 | srf.degree_u = 1; 12 | srf.degree_v = 1; 13 | srf.knots_u = {0, 0, 1, 1}; 14 | srf.knots_v = {0, 0, 1, 1}; 15 | // 2x2 grid (tinynurbs::array2) of control points 16 | srf.control_points = {2, 2, 17 | {glm::vec3(-1, 0, 1), glm::vec3(-1, 0, -1), 18 | glm::vec3(1, 0, 1), glm::vec3(1, 0, -1) 19 | } 20 | }; 21 | return srf; 22 | } 23 | 24 | TEST_CASE("surfacePoint (non-rational)", "[surface, non-rational, evaluate]") 25 | { 26 | auto srf = getBilinearPatch(); 27 | glm::vec3 pt1 = tinynurbs::surfacePoint(srf, 0.f, 0.f); 28 | glm::vec3 pt2 = tinynurbs::surfacePoint(srf, 0.5f, 0.5f); 29 | REQUIRE(pt1.x == Approx(-1)); 30 | REQUIRE(pt1.y == Approx(0)); 31 | REQUIRE(pt1.z == Approx(1)); 32 | REQUIRE(pt2.x == Approx(0)); 33 | REQUIRE(pt2.y == Approx(0)); 34 | REQUIRE(pt2.z == Approx(0)); 35 | } 36 | 37 | TEST_CASE("surfaceTangent (non-rational)", "[surface, non-rational, evaluate]") 38 | { 39 | auto srf = getBilinearPatch(); 40 | glm::vec3 tgt_u, tgt_v; 41 | std::tie(tgt_u, tgt_v) = tinynurbs::surfaceTangent(srf, 0.5f, 0.25f); 42 | REQUIRE(glm::length(tgt_u) == Approx(1)); 43 | REQUIRE(tgt_u.x == Approx(1)); 44 | REQUIRE(tgt_u.y == Approx(0)); 45 | REQUIRE(tgt_u.z == Approx(0)); 46 | REQUIRE(glm::length(tgt_v) == Approx(1)); 47 | REQUIRE(tgt_v.x == Approx(0)); 48 | REQUIRE(tgt_v.y == Approx(0)); 49 | REQUIRE(tgt_v.z == Approx(-1)); 50 | } 51 | 52 | 53 | TEST_CASE("surfaceNormal (non-rational)", "[surface, non-rational, evaluate]") 54 | { 55 | auto srf = getBilinearPatch(); 56 | glm::vec3 n = tinynurbs::surfaceNormal(srf, 0.5f, 0.5f); 57 | REQUIRE(glm::length(n) == Approx(1)); 58 | REQUIRE(n.x == Approx(0)); 59 | REQUIRE(n.y == Approx(-1)); 60 | REQUIRE(n.z == Approx(0)); 61 | 62 | n = tinynurbs::surfaceNormal(srf, 0.25f, 0.75f); 63 | REQUIRE(glm::length(n) == Approx(1)); 64 | REQUIRE(n.x == Approx(0)); 65 | REQUIRE(n.y == Approx(-1)); 66 | REQUIRE(n.z == Approx(0)); 67 | } 68 | 69 | TEST_CASE("surfaceIsValid (non-rational)", "[surface, check]") 70 | { 71 | auto srf = getBilinearPatch(); 72 | bool is_valid = tinynurbs::surfaceIsValid(srf); 73 | REQUIRE(is_valid == true); 74 | 75 | srf = getBilinearPatch(); 76 | srf.degree_u = 5; 77 | is_valid = tinynurbs::surfaceIsValid(srf); 78 | REQUIRE(is_valid == false); 79 | 80 | srf = getBilinearPatch(); 81 | srf.degree_v = 4; 82 | is_valid = tinynurbs::surfaceIsValid(srf); 83 | REQUIRE(is_valid == false); 84 | } 85 | 86 | TEST_CASE("surfaceInsertKnotU (non-rational)", "[surface, non-rational, modify]") 87 | { 88 | auto srf = getBilinearPatch(); 89 | unsigned int repeat = 1; 90 | 91 | glm::vec3 pt = tinynurbs::surfacePoint(srf, 0.25f, 0.5f); 92 | 93 | size_t n_knots_prev = srf.knots_u.size(); 94 | size_t n_control_points_prev = srf.control_points.rows(); 95 | 96 | auto new_srf = tinynurbs::surfaceKnotInsertU(srf, 0.25f, repeat); 97 | glm::vec3 new_pt = tinynurbs::surfacePoint(new_srf, 0.25f, 0.5f); 98 | 99 | size_t n_knots_curr = new_srf.knots_u.size(); 100 | size_t n_control_points_curr = new_srf.control_points.rows(); 101 | 102 | REQUIRE((n_knots_prev + repeat) == n_knots_curr); 103 | REQUIRE((n_control_points_prev + repeat) == n_control_points_curr); 104 | REQUIRE(pt.x == Approx(new_pt.x)); 105 | REQUIRE(pt.y == Approx(new_pt.y)); 106 | REQUIRE(pt.z == Approx(new_pt.z)); 107 | } 108 | 109 | TEST_CASE("surfaceInsertKnotV (non-rational)", "[surface, non-rational, modify]") 110 | { 111 | auto srf = getBilinearPatch(); 112 | unsigned int repeat = 1; 113 | 114 | glm::vec3 pt = tinynurbs::surfacePoint(srf, 0.25f, 0.5f); 115 | 116 | size_t n_knots_prev = srf.knots_v.size(); 117 | size_t n_control_points_prev = srf.control_points.cols(); 118 | 119 | auto new_srf = tinynurbs::surfaceKnotInsertV(srf, 0.5f, repeat); 120 | glm::vec3 new_pt = tinynurbs::surfacePoint(new_srf, 0.25f, 0.5f); 121 | 122 | size_t n_knots_curr = new_srf.knots_v.size(); 123 | size_t n_control_points_curr = new_srf.control_points.cols(); 124 | 125 | REQUIRE((n_knots_prev + repeat) == n_knots_curr); 126 | REQUIRE((n_control_points_prev + repeat) == n_control_points_curr); 127 | REQUIRE(pt.x == Approx(new_pt.x)); 128 | REQUIRE(pt.y == Approx(new_pt.y)); 129 | REQUIRE(pt.z == Approx(new_pt.z)); 130 | } 131 | 132 | TEST_CASE("surfaceSplitU (non-rational)", "[surface, non-rational, modify]") 133 | { 134 | auto srf = getBilinearPatch(); 135 | float u = 0.25f; 136 | tinynurbs::Surface3f left, right; 137 | std::tie(left, right) = tinynurbs::surfaceSplitU(srf, u); 138 | 139 | bool is_valid_l = tinynurbs::surfaceIsValid(left); 140 | bool is_valid_r = tinynurbs::surfaceIsValid(right); 141 | 142 | REQUIRE(is_valid_l == true); 143 | REQUIRE(is_valid_r == true); 144 | 145 | REQUIRE(left.degree_u == srf.degree_u); 146 | REQUIRE(right.degree_u == srf.degree_u); 147 | REQUIRE(left.degree_v == srf.degree_v); 148 | REQUIRE(right.degree_v == srf.degree_v); 149 | 150 | for (int i = 0; i < left.degree_u + 1; ++i) { 151 | int d = left.knots_u.size() - (left.degree_u + 1); 152 | REQUIRE(left.knots_u[d+i] == Approx(u)); 153 | } 154 | 155 | for (int i = 0; i < right.degree_u + 1; ++i) { 156 | REQUIRE(right.knots_u[i] == Approx(u)); 157 | } 158 | 159 | tinynurbs::surfaceSaveOBJ("left_nonrational_u.obj", left); 160 | tinynurbs::surfaceSaveOBJ("right_nonrational_u.obj", right); 161 | 162 | glm::vec3 pt1 = tinynurbs::surfacePoint(srf, left.knots_u[left.knots_u.size() - 1], 0.f); 163 | glm::vec3 pt2 = tinynurbs::surfacePoint(srf, right.knots_u[0], 0.f); 164 | REQUIRE(pt1.x == Approx(pt2.x)); 165 | REQUIRE(pt1.y == Approx(pt2.y)); 166 | REQUIRE(pt1.z == Approx(pt2.z)); 167 | } 168 | 169 | TEST_CASE("surfaceSplitV (non-rational)", "[surface, non-rational, modify]") 170 | { 171 | auto srf = getBilinearPatch(); 172 | float v = 0.25f; 173 | tinynurbs::Surface3f left, right; 174 | std::tie(left, right) = tinynurbs::surfaceSplitV(srf, v); 175 | 176 | bool is_valid_l = tinynurbs::surfaceIsValid(left); 177 | bool is_valid_r = tinynurbs::surfaceIsValid(right); 178 | 179 | REQUIRE(is_valid_l == true); 180 | REQUIRE(is_valid_r == true); 181 | 182 | REQUIRE(left.degree_u == srf.degree_u); 183 | REQUIRE(right.degree_u == srf.degree_u); 184 | REQUIRE(left.degree_v == srf.degree_v); 185 | REQUIRE(right.degree_v == srf.degree_v); 186 | 187 | for (int i = 0; i < left.degree_v + 1; ++i) { 188 | int d = left.knots_v.size() - (left.degree_v + 1); 189 | REQUIRE(left.knots_v[d+i] == Approx(v)); 190 | } 191 | 192 | for (int i = 0; i < right.degree_v + 1; ++i) { 193 | REQUIRE(right.knots_v[i] == Approx(v)); 194 | } 195 | 196 | tinynurbs::surfaceSaveOBJ("left_nonrational_v.obj", left); 197 | tinynurbs::surfaceSaveOBJ("right_nonrational_v.obj", right); 198 | 199 | glm::vec3 pt1 = tinynurbs::surfacePoint(srf, left.knots_v[left.knots_v.size() - 1], 0.f); 200 | glm::vec3 pt2 = tinynurbs::surfacePoint(srf, right.knots_v[0], 0.f); 201 | REQUIRE(pt1.x == Approx(pt2.x)); 202 | REQUIRE(pt1.y == Approx(pt2.y)); 203 | REQUIRE(pt1.z == Approx(pt2.z)); 204 | } 205 | 206 | TEST_CASE("surfaceReadOBJ and surfaceSaveOBJ (non-rational)", "[surface, non-rational, obj]") 207 | { 208 | auto srf = getBilinearPatch(); 209 | 210 | tinynurbs::surfaceSaveOBJ("surface_nonrational.obj", srf); 211 | auto read_srf = tinynurbs::surfaceReadOBJ("surface_nonrational.obj"); 212 | 213 | REQUIRE(srf.degree_u == read_srf.degree_u); 214 | REQUIRE(srf.degree_v == read_srf.degree_v); 215 | REQUIRE(srf.knots_u.size() == read_srf.knots_u.size()); 216 | for (int i = 0; i < srf.knots_u.size(); ++i) { 217 | REQUIRE(srf.knots_u[i] == Approx(read_srf.knots_u[i])); 218 | } 219 | REQUIRE(srf.knots_v.size() == read_srf.knots_v.size()); 220 | for (int i = 0; i < srf.knots_v.size(); ++i) { 221 | REQUIRE(srf.knots_v[i] == Approx(read_srf.knots_v[i])); 222 | } 223 | REQUIRE(srf.control_points.rows() == read_srf.control_points.rows()); 224 | REQUIRE(srf.control_points.cols() == read_srf.control_points.cols()); 225 | for (int i = 0; i < srf.control_points.rows(); ++i) { 226 | for (int j = 0; j < srf.control_points.cols(); ++j) { 227 | REQUIRE(srf.control_points(i, j).x == Approx(read_srf.control_points(i, j).x)); 228 | REQUIRE(srf.control_points(i, j).y == Approx(read_srf.control_points(i, j).y)); 229 | REQUIRE(srf.control_points(i, j).z == Approx(read_srf.control_points(i, j).z)); 230 | } 231 | } 232 | } --------------------------------------------------------------------------------