Nurbs曲线

news2024/12/23 4:34:27

本文深入探讨了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(一)贝塞尔曲线 - 知乎

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[FlareOn1]Bob Doge

[FlareOn1]Bob Doge Hint:本题解出相应字符串后请用flag{}包裹&#xff0c;形如&#xff1a;flag{123456flare-on.com} 得到的 flag 请包上 flag{} 提交。 密码&#xff1a;malware 没什么思路&#xff0c;原exe文件运行又install了一个challenge1.exe文件 c#写的&#xff…

618购物狂欢不知道怎么买?请收下这份好物清单,直接闭眼入!

在繁忙的618购物狂欢节来临之际&#xff0c;面对琳琅满目的商品&#xff0c;你是否感到无从下手&#xff1f;别担心&#xff0c;我们精心整理了一份好物清单&#xff0c;汇聚了各类热销与口碑兼具的精品。无论你是追求品质生活的消费者&#xff0c;还是寻找实惠好物的网购达人&…

618值得入手的数码产品怎么选?2024 买过不后悔的数码好物分享

在数字时代的浪潮中&#xff0c;每一次的购物狂欢节都如同一场科技盛宴&#xff0c;让我们有机会接触到最前沿、最实用的数码产品&#xff0c;而“618”无疑是这场盛宴中最为引人瞩目的日子之一。面对琳琅满目的商品&#xff0c;如何选择那些真正值得入手的数码好物&#xff0c…

社交媒体数据恢复:派派

派派是一款非常流行的社交软件&#xff0c;但是如果你在使用派派的过程中&#xff0c;不小心删除了一些重要的聊天记录或者其他数据&#xff0c;那么该怎么办呢&#xff1f;下面是一些简单的步骤&#xff0c;可以帮助你进行数据恢复。 1. 首先打开派派&#xff0c;并进入需要恢…

idea使用gitee基本操作流程

1.首先&#xff0c;每次要写代码前&#xff0c;先切换到自己负责的分支 点击签出。 然后拉取一次远程master分支&#xff0c;保证得到的是最新的代码。 写完代码后&#xff0c;在左侧栏有提交按钮。 点击后&#xff0c;选择更新的文件&#xff0c;输入描述内容&#xff08;必填…

深度解析Nginx:高性能Web服务器的奥秘(下)

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《洞察之眼&#xff1a;ELK监控与可视化》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、前言 1、Nginx概述 二、Nginx核心功能 1、URL重写与重…

C语言易错提醒选择题精选

Ⅰ 易错题 1.设有double p;&#xff0c;为变量p声明一个引用名称rp,则定义语句为 double& rpp; 2.已知‘A’一‘Z’的ASCII码为65—90&#xff0c;当执行“char ch14*52&#xff1b;cout<<ch<<endl;”语句序列后得到的输出结H &#xff0c;72对应ASCII码中…

视觉SLAM十四讲:从理论到实践(Chapter3:三维空间刚体运动)

前言 学习笔记&#xff0c;仅供学习&#xff0c;不做商用&#xff0c;如有侵权&#xff0c;联系我删除即可 目标 理解三维空间的刚体运动描述方式&#xff1a;旋转矩阵、变换矩阵、四元数和欧拉角。掌握Eigen库的矩阵、几何模块的使用方法。 3.1 旋转矩阵 3.1.1 点、向量和…

详解xlsxwriter 操作Excel的常用API

我们知道可以通过pandas 对excel 中的数据进行处理分析&#xff0c;但是pandas本身对格式化数据方面提供了很少的支持&#xff0c;如果我们想对pandas进行数据分析后的数据进行格式化相关操作&#xff0c;我们可以使用xlsxwriter&#xff0c;本文就对xlsxwriter的常见excel格式…

一觉醒来 AI科技圈发生的大小事儿 05月13日

&#x1f4f3;博弈论让 AI 更加正确、高效&#xff0c;LLM 与自己竞争 研究团队设计了共识博弈&#xff0c;通过让语言模型的生成器和判别器相互博弈来提高模型的准确性和内部一致性。这种方法不需要对基础模型进行训练或修改&#xff0c;可以在笔记本电脑上快速执行。研究结果…

山洪灾害无线预警广播系统的主要设备和功能

一、背景 山洪泥石流是指在山区或者其他沟谷深壑&#xff0c;地形险峻的地区&#xff0c;因为暴雨、暴雪或其他自然灾害引发的洪水、山体滑坡并携带有大量泥沙以及石块的特殊洪流。山洪泥石流等地质灾害具有突然性以及流速快&#xff0c;流量大&#xff0c;物质容量大和破坏力…

C# SortedList 用法

文章目录 基本用法主要属性和方法注意事项 SortedList 的一些高级用法和注意事项。自定义排序规则线程安全性性能考量与其他集合的对比 SortedList 是 C# 中的一个集合类&#xff0c;它是一个键/值对集合&#xff0c;其中的键自动按顺序排序。这个类位于 System.Collections.G…

在Windows环境下安装CPU版的PyTorch

PytTorch是基于Python开发的&#xff0c;首先需要安装Python&#xff0c;Python的安装很简单&#xff0c;这里不再赘述。而 Windows用户能直接通过conda、pip和源码编译三种方式来安装PyTorch。 打开PyTorch官网&#xff08;PyTorch&#xff09;&#xff0c;在主页中根据自己的…

2024年NOC大赛创客智慧(西瓜创客)Python复赛编程真题模拟试卷包含答案

NOC复赛python模拟题 1.编写一个程序&#xff0c;提示用户输人一个矩形的长度和宽度&#xff0c;并输出其面积, 2.试计算在区间 1 到 n的所有整数中,数字x(0≤x≤9)共出现了多少次?例如在 1到11 中&#xff0c;即在 1,2,3.45,6.7,8.9,10,11 中&#xff0c;数字 1出现了 4 次.…

买货查窜货过程中的可能情况

控价除了要管控渠道中的低价、乱价链接外&#xff0c;还可能需要解决窜货问题&#xff0c;当窜货问题蔓延不及时解决时&#xff0c;渠道会越来越受影响&#xff0c;所以治理窜货也是控价过程中很重要的一步&#xff0c;窜货问题的治理多通过买货溯源来解决&#xff0c;买货要先…

关于LED的小事

基础知识 LED&#xff08;Light Emitting Diode&#xff09;是一种能够将电能转换为光能的发光二极管。LED的发明者是美国的物理学家罗伯特诺伊斯和化学家哈里贾斯特。LED的原理是利用半导体材料中的电子和空穴在禁带中产生复合&#xff0c;从而释放出光子&#xff0c;达到发光…

QLExpress入门及实战总结

文章目录 1.背景2.简介3.QLExpress实战3.1 基础例子3.2 低代码实战3.2.1 需求描述3.2.1 使用规则引擎3.3.2 运行结果 参考文档 1.背景 最近研究低代码实现后端业务逻辑相关功能&#xff0c;使用LiteFlow作为流程编排后端service服务, 但是LiteFlow官方未提供图形界面编排流程。…

深入 Go 语言:使用 math/rand 包实现高效随机数生成

深入 Go 语言&#xff1a;使用 math/rand 包实现高效随机数生成 介绍math/rand 包的核心功能设计哲学应用场景 基础使用方法初始化和种子设置设置种子创建私有随机数生成器 基础函数详解生成整数生成特定范围的整数生成浮点数随机置乱数组 进阶技巧随机数的统计属性生成正态分布…

背背佳卷土重来90天爆卖一个亿,这次盯上了成年人……

提起背背佳这三个字&#xff0c;除了00后不熟悉外&#xff0c;在座的柴油们应该没有陌生的吧&#xff01;不管你是90后&#xff0c;80后&#xff0c;还是70后&#xff0c;60后。 但是&#xff0c;似乎好多年&#xff0c;这三个字没出现过了。 但是这两天&#xff0c;背背佳这三…

嵌入式STM32中I2C控制器外设详解

STM32中的I2C外设主要负责IIC协议与外界进行通信,就像USART外设一样,我们在学习的过程中,需要抓住I2C应用的重点。 STM32在使用I2C协议时,可以通过两种方式, 一是软件模拟协议 意思是使用CPU直接控制通讯引脚的电平,产生出符合通讯协议标准的逻辑。例如,像点亮LED那样…