Nurbs曲线

本文深入探讨了Nurbs曲线的概念、原理及应用,揭示了其在数字设计领域的独特价值和广泛影响。Nurbs曲线作为一种强大的数学工具,为设计师们提供了更加灵活、精确的曲线创建方式,从而极大地提升了设计作品的质感和表现力。文章首先介绍了Nurbs曲线的基本概念和数学原理,进而分析了其在工业设计、动画制作、3D打印等领域的实际应用。此外,文章还探讨了Nurbs曲线的未来发展前景,以及它如何继续推动数字设计领域的创新和进步。

1. 基本理论

NURBS是非均匀有理B样条(Non-Uniform Rational B-Splines)的缩写。

学习得从更特殊的形式学起,否则会比较懵。来看看各种曲线的关系图。

所以,似乎先学贝塞尔曲线应该是个好的开始。

2. 代码实现

辅助结构体定义

struct ColOfMatrix {
  int32_t start_;
  int32_t end_;
  std::vector<double> vec_;
  ColOfMatrix() : start_(-1), end_(0) {
  }
};

分割参数空间

template<class Point3_T>
bool splitParamSpace(const std::vector<Point3_T> &vInputPoint,
                     const std::vector<int> &vDPIndex,
                     std::vector<float> &vU,
                     std::vector<float> &vKnot) const {
  bool bRet = true;
  if (vInputPoint.empty() || vDPIndex.empty()) {
    LOG(ERROR) << "vInputPoint.size() = " << vInputPoint.size() << ", vDPIndex.size() = " << vDPIndex.size();
    bRet = false;
  } else if (vDPIndex.front() != 0 || vDPIndex.back() != static_cast<int>(vInputPoint.size()) - 1) {
    LOG(ERROR) << "The first and last element of vDPIndex is: " << vDPIndex.front() << ", " << vDPIndex.back()
               << "vInputPoint.size() = " << vInputPoint.size();
    bRet = false;
  } else {
    int32_t numDistance = static_cast<int>(vInputPoint.size()) - 1;
    std::vector<float> vDistance;
    vDistance.reserve(numDistance);

    double tmpDistance, tmpDiff;
    for (auto first = vInputPoint.begin(), second = first + 1; second < vInputPoint.end(); ++first, ++second) {
      tmpDiff = first->x - second->x;
      tmpDistance = tmpDiff * tmpDiff;

      tmpDiff = first->z - second->z;
      tmpDistance += tmpDiff * tmpDiff;

      vDistance.emplace_back(std::sqrt(tmpDistance));
    }

    double sumDistance = std::accumulate(vDistance.begin(), vDistance.end(), 0.f);

    //Generate the vector U, U is u bar
    vU.clear();
    vU.reserve(vInputPoint.size());

    if (sumDistance < 0.1f) {
      LOG(ERROR) << "The line is too short.";
      bRet = false;
    } else {
      double divisor = 1.f / sumDistance, elem(0);
      for (auto it = vDistance.begin(); it < vDistance.end(); ++it) {
        vU.emplace_back(elem);
        elem += (*it) * divisor;
        if (elem - 1.f > FLT_MIN) {
          elem = 1.f;
        }
      }
      vU.emplace_back(1.f);
    }

    if (bRet) {
      //generate knot
      vKnot.clear();
      vKnot.reserve(vDPIndex.size() + 4u);
      vKnot.emplace_back(0);
      vKnot.emplace_back(0);

      for (auto it = vDPIndex.begin(); it < vDPIndex.end(); ++it) {
        vKnot.emplace_back(vU[*it]);
      }

      vKnot.emplace_back(1);
      vKnot.emplace_back(1);
    }
  }

  return bRet;
}

计算控制点到曲线的映射矩阵

bool solveMatrixN(int32_t numCtrPoints,
                  const std::vector<double> &knot,
                  const std::vector<double> &U,
                  std::vector<ColOfMatrix> &matrixN) const {
  int32_t numPoints = static_cast<int32_t>(U.size());
  if (numCtrPoints > numPoints || knot.empty() || U.empty()) {
    LOG(ERROR) << "The arguments is error in function solveMatrixN.";
    return false;
  }

  int32_t degree = configPara_.order - 1;
  matrixN.clear();
  matrixN.resize(numCtrPoints);

  int32_t index, s = degree;
  std::vector<double> N;          //It is a temporary variable
  for (int32_t row = 0; row < numPoints; ++row) {
    if (!findSpan(numCtrPoints - 1, s, U[row], knot, s)) {
      return false;
    }

    if (!basisFun(s, U[row], degree, knot, N)) {
      return false;
    }

    index = s - degree;
    for (int32_t i = 0; i <= degree; ++i) {
      if (matrixN[index + i].start_ == -1) {
        matrixN[index + i].start_ = row;
      }
      matrixN[index + i].vec_.emplace_back(N[i]);
    }
  }

  std::for_each(matrixN.begin(), matrixN.end(), [](ColOfMatrix &elem) {
    elem.end_ = elem.start_ + static_cast<int32_t>(elem.vec_.size());
  });

  return true;
}

求解控制点

template<class Point3_T>
bool solveCtrPoints(const std::vector<Point3_T> &inputPoints,
                    const std::vector<ColOfMatrix> &matrixN,
                    const std::vector<Point3_T> &R,
                    std::vector<Point3d> &ctrPoints) const {
  //matrixN represent a matrix
  if (inputPoints.empty() || matrixN.empty() || R.empty()) {
    LOG(ERROR) << "The arguments are invalid in function solveCtrPoints.";
    return false;
  }

  int32_t cols = static_cast<int32_t>(matrixN.size());  //Every element of matrixN is a column of a matrix
  if (cols - 2 != static_cast<int32_t>(R.size())) {
    LOG(ERROR) << "The vector R does not match the matrix NRIO";
    return false;
  }

  //calculate (matrixN^T * matrixN)
  cv::Mat squareMatrix = cv::Mat::zeros(cols, cols, CV_32F); // square matrix, squareMatrix = matrixN' * matrixN
  auto itr = matrixN.begin();
  for (int32_t r = 0; r < cols; ++r)  // square matrix
  {
    auto p = squareMatrix.ptr<double>(r);
    double tmp(0);
    for (auto itCol = itr->vec_.begin(); itCol < itr->vec_.end(); ++itCol) {
      tmp += (*itCol) * (*itCol);
    }
    p[r] = tmp
      * 0.5f; // because it calculate a half of the matrix only, this function will sum this matrix and it's transposition
    auto itc = itr + 1;

    for (int32_t c = r + 1; c < cols; ++c)  // square matrix
    {
      auto length = itr->end_ - itc->start_;
      if (length <= 0) {
        break;
      } else if (length > static_cast<int32_t>(itc->vec_.size())) {
        length = itc->vec_.size();
      } else {
      }

      tmp = 0;
      auto index = itc->start_ - itr->start_;
      for (int32_t i = 0; i < length; ++i) {
        tmp += itr->vec_[index] * itc->vec_[i];
        ++index;
      }

      p[c] = tmp;
      ++itc;
    }
    ++itr;
  }

  squareMatrix = squareMatrix + squareMatrix.t();

  cv::Rect center(1, 1, cols - 2, cols - 2);
  cv::Mat sMatrix = squareMatrix(center);

  //calculate (matrixN^T * matrixN)
  sMatrix = sMatrix.inv(cv::DECOMP_LU);

  //calculate control points. control points = squareMatrix*R
  ctrPoints.clear();
  ctrPoints.reserve(cols);
  cols = sMatrix.cols;

  Point3d tmpPoint, zeroPoint(0, 0, 0);

  auto &point1 = inputPoints.front();
  tmpPoint.x = point1.x;
  tmpPoint.y = point1.y;
  tmpPoint.z = point1.z;

  ctrPoints.emplace_back(tmpPoint);  //the first control point is the first point of the curve
  for (int32_t i = 0; i < cols; ++i) {
    tmpPoint = zeroPoint;
    double *q = sMatrix.ptr<double>(i);
    auto itR = R.begin();
    for (int32_t j = 0; j < cols; ++j) {
      const auto &element = q[j];
      tmpPoint.x += itR->x * element;
      tmpPoint.y += itR->y * element;
      tmpPoint.z += itR->z * element;
      ++itR;
    }
    ctrPoints.emplace_back(tmpPoint);
  }

  auto &point2 = inputPoints.back();
  tmpPoint.x = point2.x;
  tmpPoint.y = point2.y;
  tmpPoint.z = point2.z;
  ctrPoints.emplace_back(tmpPoint); //the last control point is the last point of the curve

  return true;
}

计算RHS矩阵


template<class Point3_T>
bool generateR(const std::vector<ColOfMatrix> &matrixN,
               const std::vector<Point3_T> &inputPoints,
               std::vector<Point3_T> &R) const {
  if (inputPoints.empty() || matrixN.empty()) {
    LOG(ERROR) << "No input Points in generateR";
    return false;
  }

  // It equal to the number of control Points. Every element of matrixN is a column of a matrix
  int32_t cols = static_cast<int32_t>(matrixN.size());
  if (cols < 2) {
    LOG(ERROR) << "data error.";
    return false;
  }
  int32_t rows = static_cast<int32_t>(inputPoints.size());

  std::vector<Point3_T> Rmatrix(rows);
  ColOfMatrix firstCol = matrixN.front(), lastCol = matrixN.back();
  double element1, element2;
  for (int32_t k = 1; k < rows - 1; ++k) {
    if (k < firstCol.start_ || k >= firstCol.end_) {
      element1 = 0;
    } else {
      element1 = firstCol.vec_[k - firstCol.start_];
    }

    if (k < lastCol.start_ || k >= lastCol.end_) {
      element2 = 0;
    } else {
      element2 = lastCol.vec_[k - lastCol.start_];
    }

    //Rmatrix[k] = inputPoints[k] - element1 * inputPoints.front() - element2 * inputPoints.back()
    Rmatrix[k] = subtract(inputPoints[k],
                          add(numericalMultiply(element1, inputPoints.front()),
                              numericalMultiply(element2, inputPoints.back())));
  }

  R.clear();
  R.reserve(cols - 2);

  auto itN = matrixN.begin() + 1;
  int32_t startIdxN, endIdxN, startIdxR;
  Point3_T tmpPoint;
  for (int32_t i = 1; i < cols - 1; ++i) {
    if (itN->start_ < 1) {
      startIdxR = 1;
      startIdxN = 1 - itN->start_;
    } else {
      startIdxR = itN->start_;
      startIdxN = 0;
    }

    if (itN->end_ > rows - 1) {
      endIdxN = rows - 1 - itN->start_;
    } else {
      endIdxN = itN->end_ - itN->start_;
    }

    auto it = Rmatrix.begin() + startIdxR;

    reset(tmpPoint);
    for (int32_t j = startIdxN; j < endIdxN; ++j) {
      tmpPoint += numericalMultiply(itN->vec_[j], *it);
      ++it;
    }
    R.emplace_back(tmpPoint);
    ++itN;
  }

  return true;
}

 其它辅助函数

//This function is called frequently
bool basisFun(const int32_t s,
              const double u,
              const int32_t p,
              const std::vector<double> &U,
              std::vector<double> &N) const {
  if (!U.empty()) {
    std::vector<double> left(p + 1, 0.0f);
    std::vector<double> right(p + 1, 0.0f);

    N.clear();
    N.resize(p + 1, 0.0f);
    N[0] = 1.0f;

    double saved, tmp;
    for (int32_t j = 1; j <= p; ++j) {
      left[j] = u - U[s + 1 - j];
      right[j] = U[s + j] - u;
      saved = 0.0f;

      for (int32_t r = 0; r < j; ++r) {
        tmp = N[r] / (right[r + 1] + left[j - r]);
        N[r] = saved + right[r + 1] * tmp;
        saved = left[j - r] * tmp;
      }

      N[j] = saved;
    }

    return true;
  } else {
    LOG(INFO) << "Input data are empty";
    return false;
  }
}

//This function is called frequently
bool findSpan(const int32_t n, const int32_t p, double u, const std::vector<double> &knot, int32_t &s) const {
  if (n < static_cast<int32_t>(knot.size()) && p <= n) {
    if (u < knot[p]) {
      s = p;
    } else if (u > knot[n]) {
      s = n;
    } else {
      int32_t low = p;
      int32_t high = n + 1;
      int32_t mid = low + (high - low) / 2; // mid = floor((high+low)/2)

      while (low + 1 < high) // +1 for low = high -1 case
      {
        if (u - knot[mid] < -FLT_MIN) {
          high = mid;
        } else if (u - knot[mid + 1] > -FLT_MIN) {
          low = mid;
        } else {
          break;
        }

        mid = low + (high - low) / 2;  // mid = floor((high+low)/2)
      }
      s = mid;
    }

    return true;
  } else {
    LOG(ERROR) << "The arguments are invalid in function findSpan.";
    return false;
  }
}

重建

template<class Point3_T>
bool reconstruct(const NurbsParam &NURBSParam,
                 const float step,
                 std::vector<std::vector<Point3_T>> &vvOutPoints) const {
  if (NURBSParam.vecCtrlPoint.empty() || step < FLT_EPSILON || step > NURBSParam.paintTotalLength / 3.0f) {
    LOG(ERROR) << "The arguments are invalid in function reconstruct.";
    return false;
  }

  int32_t outputPointNum = static_cast<int32_t>(std::ceil(NURBSParam.paintTotalLength / step));

  // Safety guard
  outputPointNum = std::max(outputPointNum, 2);

  std::vector<int32_t> vecIdx;
  vecIdx.reserve(NURBSParam.endPoint.size());

  if (NURBSParam.endPoint.empty()) {
    LOG(ERROR) << "The endPoint is empty.";
    return false;
  } else if ((NURBSParam.endPoint.size() & 0x1) != 0) {
    LOG(ERROR) << "The number of end points must be even.";
    return false;
  } else {
    for (auto it1 = NURBSParam.endPoint.begin(), it2 = it1 + 1; it2 < NURBSParam.endPoint.end(); ++it1, ++it2) {
      if (*it1 > *it2) {
        LOG(ERROR) << "The arguments are invalid in function generateCurve.";
        return false;
      }
    }

    for (auto it1 = NURBSParam.vecKnot.begin(), it2 = it1 + 1; it2 < NURBSParam.vecKnot.end(); ++it1, ++it2) {
      if (*it1 > *it2) {
        LOG(ERROR) << "The arguments are invalid in function generateCurve.";
        return false;
      }
    }
  }

  float length(0);                //the length of the paint
  auto it = NURBSParam.endPoint.begin();
  while (it < NURBSParam.endPoint.end()) {
    length -= *(it++);
    length += *(it++);
  }

  float scale = length / (outputPointNum - 1);   // outputPointNum >= 2

  if (scale < FLT_EPSILON) {
    LOG(INFO) << "The input data are invalid.";
    return false;
  }

  it = NURBSParam.endPoint.begin();
  float tmp = *(it++);

  std::vector<double> U;
  U.reserve(outputPointNum);
  int32_t i = 0;
  while (it < NURBSParam.endPoint.end()) {
    U.emplace_back(tmp);
    ++i;
    tmp += scale;
    // if tmp > the second end point of a segment, then set tmp as the first end point of next segment.
    if (tmp > *it) {
      U.emplace_back(*(it++));
      vecIdx.emplace_back(++i);

      if (it == NURBSParam.endPoint.end()) {
        break;
      } else {
        tmp = *(it++);
      }
    }
  }

  std::vector<Point3_T> outputPoints;
  if (!outSample(NURBSParam, U, outputPoints)) {
    LOG(ERROR) << "Sample error";
    return false;
  }

  vvOutPoints.clear();
  vvOutPoints.resize(vecIdx.size());

  auto begin = outputPoints.begin();
  auto first = begin, last = begin;

  for (size_t i = 0; i < vvOutPoints.size(); ++i) {
    last = begin + vecIdx[i];
    vvOutPoints[i].insert(vvOutPoints[i].end(), first, last);
    first = last;
  }

  return true;
}

参考文献

Nurbs曲线详解_123_jason的博客-CSDN博客_nurbs曲线

NURBS_百度百科

NURBS(一): 动机、定义以及历史漫谈 - Fun With GeometryFun With Geometry

NURBS(一)贝塞尔曲线 - 知乎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/623247.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大数据之 Hadoop概述

用最简洁的语言跟大家表达我最想分享的知识 。 什么是Hadoop Hadoop框架核心模块 HDFS MapReduce Yarn Hive HBase Phoenix Zookeeper Impala Spark 分布式计算-Spark与Impala与Presto与Tez 今天主要跟大家简述一下hadoop&#xff0c;主要是图片的形式跟大家介绍&#xff0c;希…

Rpcx (二):传输

一、Transport 传输 rpcx 可以通过 TCP、HTTP、UnixDomain、QUIC和KCP通信。你也可以使用http客户端通过网关或者http调用来访问rpcx服务。 TCP 这是最常用的通信方式。高性能易上手。可以使用TLS加密TCP流量。 Example: 101basic 服务端使用 tcp 做为网络名并且在注册中心…

稚晖君独家撰文:具身智能即将为通用机器人补全最后一块拼图

具身智能新纪元。 *本文为稚晖君独家供稿,「甲子光年」经智元机器人授权发布。稚晖君本名彭志辉,先后任职OPPO、华为,现为智元机器人CTO、首席架构师。 在ChatGPT之后,又一个大模型概念火了——具身智能(Embodied AI)。 在学术界,图灵奖得主、上海期智研究院院长姚期…

IOS 苹果IAP(内购)之创建沙盒账号

IOS 苹果IAP&#xff08;内购&#xff09;之创建沙盒账号 沙盒账号是什么&#xff1f;沙盒账号创建的前提条件沙盒账号创建沙盒账号使用流程沙盒账号注意事项 沙盒账号是什么&#xff1f; 如果IOS应用里面用到了苹果应用内付费&#xff08;IAP&#xff09;功能&#xff0c;那么…

办公软件_EdrawMax 免安装版教程 (亿图图示综合图形图表设计软件)

前言 万兴亿图图示(Wondershare EdrawMax)是一款综合图形图表设计软件,Visio国产替代.亿图图示中文版(Edraw Max)是一款办公绘图软件的思维导图软件.无需任何绘图功底,即可轻松创建各类思维导图.亿图图示专家,提供大量事例和在线模板,用于创建流程图,信息图,组织结构图,科学教…

面试题:调整数字顺序,使奇数位于偶数前面

题目&#xff1a; 输入一个整数数组&#xff0c;实现一个函数&#xff0c;来调整该数组中数字的顺序 使得所有奇数位于数组的前半部分&#xff0c;所有偶数位于数组的后半部分 算法1&#xff1a; 利用快速排序的一次划分思想&#xff0c;从2端往中间遍历 时间复杂度&#x…

day5

利用迭代器&#xff01; #include <vector> #include <map>class Solution { public:std::vector<int> intersection(std::vector<int>& nums1, std::vector<int>& nums2) {std::map<int, int> Mymap;std::vector<int> qq…

金万维动态域名小助手怎么用?

金万维动态域名小助手是一个域名检测工具&#xff0c;使用此工具可以进行检测域名解析是否正确、清除DNS缓存、修改DNS服务器地址及寻找在线客服&#xff08;仅支持付费用户&#xff09;等操作。对不懂网络的用户是一个很好的检测域名的工具&#xff0c;下面我就讲解一下金万维…

面试中的算法(查找缺失的整数)

在一个无序数组里有99个不重复的正整数&#xff0c;范围是1~100&#xff0c;唯独缺少1个1~100中的整数。如何找出这个缺失的整数? 一个很简单也很高效的方法&#xff0c;先算出1~100之和&#xff0c;然后依次减去数组里的元素&#xff0c;最后得到的差值&#xff0c;就是那个缺…

ARM架构安全特性之通用平台安全服务

安全之安全(security)博客目录导读 目录 一、符合PSA认证标准 二、Arm平台安全规范 三、跨安全边界通信 四、FF-A 五、FF-M 六、开放和标准设备固件 七、Trustedfirmware.org 在一个需要高度信任设备的世界中&#xff0c;每个设备都必须是独一无二的可识别的、不可克隆…

网站服务器备案及域名购买配置教程

一、阿里云服务备案准备工作 1.什么是备案? 备案是指向相关部门提交网站信息,以便监管和管理互联网信息服务,未经备案的网站可能面临罚款甚至被关闭的风险。备案主要看您的网站或App等互联网信息服务解析到的服务器是否在中国内地(大陆),如果服务器在中国内地(大陆),…

携手鲲鹏昇腾 HashData展现云原生数仓创新力量

​5月9日-11日&#xff0c;鲲鹏昇腾开发者大会2024在北京中关村国际创新中心举行&#xff0c;众多行业领袖、专家学者及优秀开发们齐聚一堂&#xff0c;分享产业趋势、技术创新和应用实践。 酷克数据作为华为鲲鹏生态重要合作伙伴&#xff0c;受邀出席本次大会&#xff0c;展示…

springboot学习整理

视频&#xff1a;基础篇-01_springboot概述_哔哩哔哩_bilibili 介绍 spring boot 是spring提供的一个子项目&#xff0c;用于快速构建spring应用程序 spring构建&#xff1a; 1 导入依赖繁琐 &#xff1b; 2 项目配置繁琐 spring Framework: 核心 spring Boot :快速构建spring…

Spring的IOC和AOP机制?

我们是在使用Spring框架的过程中&#xff0c;其实就是为了使用IOC&#xff0c;依赖注入&#xff0c;和AOP&#xff0c;面向切面编程&#xff0c;这两个是Spring的灵魂。 主要用到的设计模式有工厂模式和代理模式。 IOC就是典型的工厂模式&#xff0c;通过sessionfactory去注入…

能效?性能?一个关于Windows下使用openssl speed进行速度测试的诡异问题

问题描述 最近的某个软件用到了openssl&#xff0c;所以就想着测试一下速度。我的电脑是惠普的&#xff0c;CPU是AMD Ryzen 7 PRO 6850HS&#xff0c;系统是Win11。我使用openssl自带的speed测试加密/解密的速度&#xff0c;命令大致如下&#xff1a; openssl speed -evp aes…

【卫星影像三维重建-全流程代码实现】点云Mesh重构

点云—>Mesh模型 1.介绍1.1 背景1.2 效果示意 2 算法实现2.1 依赖库2.2 实验数据2.3 代码实现2.4 实验效果 3.总结 1.介绍 1.1 背景 &#xff08;1&#xff09;本文主要内容是将三维点云&#xff08;离散的三维点&#xff09;进行表面重建生成Mesh网格&#xff0c;之前有篇…

Day27

回溯算法part01 回溯算法 回溯算法的本质&#xff1a;本质是穷举&#xff0c;穷举所有可能&#xff0c;然后选出我们想要的答案 更高效的回溯算法&#xff1a;加入剪枝操作 回溯算法可以解决的问题类型 组合问题&#xff1a;N个数里面按一定规则找出k个数的集合切割问题&…

VRRP协议-负载分担配置【分别在路由器与交换机上配置】

VRRP在路由器与交换机上的不同配置 一、使用路由器实现负载分担二、使用交换机实现负载分担一、使用路由器实现负载分担 使用R1与R2两台设备分别进行VRRP备份组 VRRP备份组1,虚拟pc1的网关地址10.1.1.254 VRRP备份组2,虚拟pc2的网关地址10.1.1.253 ①备份组1的vrid=1,vrip=…

叉车AGV销量19.5万台,订单暴增46%,这10家公司展开激烈厮杀~

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》 在2023年的中国&#xff0c;无人叉车市场迎来了爆炸性的增长。根据CMR产业联盟和新战略移动机器人产业研究所的统计&#xff0c;全年销量达到了惊…

java 使用hh或者HH异常

故障描述 使用了HH或者hh使用时间format、DatetimeFormat注解时序列化失败 故障原因 当使用hh的时候&#xff0c;小时只能是1-24 使用KK的时候&#xff0c;小时只能是0-23 比如&#xff1a;凌晨0:30&#xff0c;使用hh就是0:30 am&#xff0c; kk就是12:30 24小时制的话,使…