/******************************************************************** 创建日期:2017年3月17日 文 件 名:PDSNurbs.h 原始作者:孙宇飞 描 述:非均匀有理B样条 修改记录: 版本号 修改日期 作者 修改内容 *********************************************************************/ #ifndef _PDS_NURBS_H_ #define _PDS_NURBS_H_ #include #include #include const double PDS_NURBS_ZERO = 0.000001; // 小于该数字的值认为等于0 //全局函数 //判断两个双精度数是否相等 BOOL PDSNurbs_IsEqual(double d1, double d2); //判断两个双精度数是否不等 BOOL PDSNurbs_NotEqual(double d1, double d2); //判断给定的双精度数是否等于0 BOOL PDSNurbs_EqualZero(double d); //判断给定的双精度数是否不为0 BOOL PDSNurbs_NotEqualZero(double d); //计算非均匀有理B样条使用的点 class CPDSNurbsPoint : public CObject { public: double x; double y; double z; double weight; /* 权重,默认值:1 // 举例:(1,1,1,1)等同于(4,4,4,4),当为某一个点设置权重的时候,一定要将xyz分量全都除以weight才能保证位置不变 */ double knot; // 该点对应的节点数值,默认值:0 int type; // 类型,=1 转折点,=0 曲线点,默认值:0,[20180322 syf]增加该变量 // 当this表示控制点时,启用该变量,那么在反求样条曲线的控制点链时,从转折点出断开,该变量不参与等号operator==的判断 public: CPDSNurbsPoint(void); CPDSNurbsPoint(CPDSNurbsPoint& rhs); CPDSNurbsPoint(CPoint ptPoint); CPDSNurbsPoint(double x, double y, double z = 0.0, double weight = 1.0); virtual ~CPDSNurbsPoint(void); CPDSNurbsPoint& operator=(CPDSNurbsPoint& rhs); CPDSNurbsPoint operator+(CPDSNurbsPoint& rhs); CPDSNurbsPoint operator-(CPDSNurbsPoint& rhs); void operator+=(CPDSNurbsPoint& rhs); BOOL operator==(CPDSNurbsPoint& rhs); void Initial(void); //将this的坐标转换为CPoint并返回 CPoint GetPoint(void); //设置this的坐标 void SetPoint(CPoint ptPoint); //乘以给定的数值 void Multiple(double dParam); //除以给定的数值 void Divide(double dParam); //将每个分量除以权重,得到规范后的坐标 void DivideWeight(void); }; //带类型的点的链表 class CPDSNurbsPointList : public CList { public: //扩展的变量 public: //扩展的方法 CPDSNurbsPointList& operator=(CPDSNurbsPointList& rhs); //赋值 CPDSNurbsPoint& operator[](int iIndex); //根据索引值检索,不可越界 //根据listPoint设置this //特别说明: // (1)this中每个点z=0,weight=1 void SetPoint(CList& listPoint); //将this的数据输出至listPoint void GetPoint(CList& listPoint); POSITION AddWithoutDuplicate(CPDSNurbsPoint ptNurbsPoint, BOOL bAddTail = TRUE); void AddWithoutDuplicate(CPDSNurbsPointList& listNurbsPoint, BOOL bAddTail = TRUE); //根据给定的节点数值,查找索引值 int FindKnotIndex(double dKnot); //获取长度 double GetLength(void); //将this翻转 void Reverse(void); }; //节点链表 //对于一条NURBS曲线来说(3次),该链表的长度应该等于控制点链长度+4 class CPDSNurbsKnotList : public CList { public: //扩展的变量 public: //扩展的方法 CPDSNurbsKnotList& operator=(CPDSNurbsKnotList& rhs); //赋值 double& operator[](int iIndex); //根据索引值检索,不可越界 //根据listKnot设置this void SetKnot(CList& listKnot); //将this的数据输出至listKnot void GetKnot(CList& listKnot); //添加新的节点,并且相邻的节点不能相同 POSITION AddWithoutDuplicate(double dKnot, BOOL bAddTail = TRUE); //查找给定的节点在this中的重复次数 int FindRepeatTime(double dKnot); }; //矩阵 class CPDSMatrix : public CObject { protected: double** m_dMatrix; // 二维数组的指针,为this私有,不同的对象之间不能共享m_dMatrix int m_iRowCount; // 行数 int m_iColumnCount; // 列数 public: CPDSMatrix(void); CPDSMatrix(CPDSMatrix& rhs); virtual ~CPDSMatrix(void); CPDSMatrix& operator=(CPDSMatrix& rhs); CPDSMatrix operator*(CPDSMatrix& rhs); // this的列数必须等于rhs的行数,否则不予计算 //初始化,用于释放内存 void Initial(void); //设置矩阵的大小 //特别说明: // 在使用矩阵的数据之前,一定要首先设置大小 void SetSize(int iRowCount, int iColumnCount); //获取矩阵的数据 void GetMatrix(double**& dMatrix, int& iRowCount, int& iColumnCount); //获取二维数组的指针 double** GetArray(void); //获取逆矩阵 //特别说明: // (1)this必须是方阵 // (2)本函数根据初等变换计算 CPDSMatrix GetInverseMatrix(void); //输出调试信息 void OutputDebugInfo(void); //将两行的数据加起来 //输入参数: // dArray1 数组1,它的数据被增加 // dArray2 数组2,其中的数据被加到dArray1,自身的数据不改变 // iCount 两个数组的长度 // dProp 比例值,dArray2[]*dProp被增加到dArray1 void AddRow(double* dArray1, double* dArray2, int iCount, double dProp = 1.0); //将数组中的每一项除以指定的参数 //输入参数: // dArray 数组 // iCount 数组的长度 // dParam 参数,数组的每一项都要除以该值 void DivideRow(double* dArray, int iCount, double dParam); }; //NURBS曲线类 //特别说明: // (1)特指3次非均匀有理B样条曲线 // (2)什么是型值点? // 指的是经过的点,在我们的系统中称之为"控制点",实际上规范化的定义叫做型值点 // 型值点也叫做拟合点 // 英文名:data point // (3)什么是控制点? // 用于计算实际点链的点,部分版师形象的称之为"手臂",曲线只会经过第一点与最后一点,不会经过中间的其他点 // 控制点链,也叫做特征多边形,它是非均匀有理B样条的凸包,曲线不会超过它的范围 // 英文名:control point // (4)NURBS曲线的方程由递推公式给出,详见相关文档 // (5)什么是节点? // 一组单调不减的数字,标识每一个control point的作用范围,对于相同的特征多边形来说,选择不同的节点,最终的效果不同 // (6)节点有多少个? // 对于degree=3来说,节点个数=特征多边形个数+degree+1=特征多边形个数+4 // 并且前degree+1=4个节点相等(未必等于0),末尾degree+1=4个节点相等(未必等于1) // 例如:{A,A,A,A,B,C,D,E,E,E,E}就是7个control point所形成的节点,每一个{A,B,C,D,E}都对应于一个data point,而这个data point在屏幕上可以看到的点 // 如果{A,B,C,D,E}构成等差数列,曲线叫做均匀有理B样条,否则叫做非均匀有理B样条 // (7)如果只有3个控制点,那么只能计算2次曲线 // (8)如果只有2个控制点,那么只能得到一条线段 class CPDSNurbsCurve : public CObject { public: //将4条链表封装在一起,方便写代码,NURBS曲线的计算函数并不使用成员变量 CPDSNurbsPointList m_listCtrlPoint; // 控制点链,也叫特征多边形,最终的实际点链只经过头尾点,而不经过中间的其他点 CPDSNurbsKnotList m_listKnot; // 节点链表,长度等于m_listCtrlPoint.GetCount()+4 CPDSNurbsPointList m_listDataPoint; // 型值点链,经过的那些点 CPDSNurbsPointList m_listRealPoint; // 实际点链,插值后的点 public: CPDSNurbsCurve(void); CPDSNurbsCurve(CPDSNurbsCurve& rhs); virtual ~CPDSNurbsCurve(void); CPDSNurbsCurve& operator=(CPDSNurbsCurve& rhs); void Initial(void); //根据型值点计算开口线 //输入参数: // listDataPoint 型值点 // dMaxError 最大误差值 // bTangentH =true 头点有切线,=false 头点没有切线 // ptTangentPointH 头点的切线方向,指的是实际点链GetPrev的方向 // bTangentT =true 尾点有切线,=false 尾点没有切线 // ptTangentPointT 尾点的切线方向,指的是实际点链GetNext的方向 //输出参数: // listOutputPoint 实际点链 //返回值: // =true 计算成功,=false 计算失败 BOOL Calculate_DataPoint_Open(CList& listDataPoint, double dMaxError, CList& listOutputPoint, BOOL bTangentH = FALSE, CPoint ptTangentPointH = CPoint(0,0), BOOL bTangentT = FALSE, CPoint ptTangentPointT = CPoint(0,0)); //根据型值点和切线点计算闭合线 //输入参数: // listDataPoint 型值点,尾点可以等于头点,也可以不等,本函数自动处理 // dMaxError 最大误差值 //输出参数: // listOutputPoint 实际点链,输出时尾点与头点重合 //返回值: // =true 计算成功,=false 计算失败 BOOL Calculate_DataPoint_Close(CList& listDataPoint, double dMaxError, CList& listOutputPoint); //根据实际点链反求型值点 //输入参数: // listOldPoint 旧的实际点链 // listDataPoint 型值点,如果输入时不为空,表示必须经过这些点 // dSplineError 计算样条曲线的最大误差值 // dMaxError 反求之后的曲线,与listOldPoint的最大误差值 // bTangentH =true 头点有切线,=false 头点没有切线 // ptTangentPointH 头点的切线方向,指的是实际点链GetPrev的方向 // bTangentT =true 尾点有切线,=false 尾点没有切线 // ptTangentPointT 尾点的切线方向,指的是实际点链GetNext的方向 //输出参数: // listDataPoint 型值点,如果输入时不为空,表示必须经过这些点 //返回值: // =true 计算成功,=false 计算失败 BOOL ReCalculate_DataPoint( CList& listOldPoint, CList& listDataPoint, double dSplineError, double dMaxError, BOOL bTangentH = FALSE, CPoint ptTangentPointH = CPoint(0,0), BOOL bTangentT = FALSE, CPoint ptTangentPointT = CPoint(0,0)); //根据给定的控制点链与节点链计算实际点链 //特别说明: // (1)本函数将会自动设置m_listCtrlPoint与m_listKnot,无需事先设置 // (2)输入时要保证listKnot.GetCount()=listCtrlPoint.GetCount()+4,这是NURBS曲线的要求,并且要保证节点数的正确性:单调不减,否则计算的结果就是错误的 // (3)从DXF等文件中读入的NURBS曲线,可以直接调用本函数来计算实际点链 //输入参数: // listCtrlPoint 控制点链 // listKnot 节点链表 // dMaxError 最大误差值 //输出参数: // listDataPoint 型值点链,即:曲线经过的那些点 // listRealPoint 实际点链 //返回值: // =true 计算成功,=false 计算失败 BOOL Calculate_CtrlPoint(CPDSNurbsPointList& listCtrlPoint, CPDSNurbsKnotList& listKnot, double dMaxError, CPDSNurbsPointList& listDataPoint, CPDSNurbsPointList& listRealPoint); //根据给定的控制点链与节点链计算型值点 //输入参数: // listCtrlPoint 控制点链 // listKnot 节点链表 //输出参数: // listDataPoint 型值点链,即:曲线经过的那些点 //返回值: // =true 计算成功,=false 计算失败 BOOL Calculate_CtrlPoint(CPDSNurbsPointList& listCtrlPoint, CPDSNurbsKnotList& listKnot, CPDSNurbsPointList& listDataPoint); protected: //根据给定的型值点计算控制点(计算开口线) //输入参数: // listDataPoint 型值点链,指的是曲线经过的那些点 // bTangentH =true 头点有切线,=false 头点没有切线 // ptTangentPointH 头点的切线方向,指的是实际点链GetPrev的方向 // bTangentT =true 尾点有切线,=false 尾点没有切线 // ptTangentPointT 尾点的切线方向,指的是实际点链GetNext的方向 //输出参数: // listCtrlPoint 控制点链,它是NURBS曲线的控制多边形或者说特征多边形,其长度=listDataPoint.GetCount()+2 // listKnot 节点链表 void CalculateCtrlPoint_Open(CPDSNurbsPointList& listDataPoint, BOOL bTangentH, CPDSNurbsPoint ptTangentPointH, BOOL bTangentT, CPDSNurbsPoint ptTangentPointT, CPDSNurbsPointList& listCtrlPoint, CPDSNurbsKnotList& listKnot); //根据给定的型值点计算控制点(闭合线) //特别说明: // (1)listInputPoint的尾点可以等于头点,也可以不等于 // (2)最终输出的listCtrlPoint头尾点相等,表示再次回到头点上 //输入参数: // listInputPoint 型值点链,指的是曲线经过的那些点 //输出参数: // listCtrlPoint 控制点链,它是NURBS曲线的控制多边形或者说特征多边形 // listKnot 节点链表 void CalculateCtrlPoint_Close(CPDSNurbsPointList& listInputPoint, CPDSNurbsPointList& listCtrlPoint, CPDSNurbsKnotList& listKnot); //获取给定线段的长度 //输入参数: // listCtrlPoint 控制点链 // iIndex 段的索引值,指的是(listCtrlPoint[iIndex], [iIndex+1])所形成的线段,例如:第0段指的是([0],[1]) //返回值: // 长度,如果传入的索引值越界则返回0 double GetSegmentLength(CPDSNurbsPointList& listCtrlPoint, int iIndex); //查找给定节点的索引值 //特别说明: // (1)如果给定的节点位于[listKnot[i],listKnot[i+1]),那么返回i // (2)注意可以等于listKnot[i],一定要严格小于[i+1] // (3)从NURBS曲线的定义可以知道,由于{A,A,A,A...}前四个数据重复,所以该函数的返回值从3开始 //输入参数: // listKnot 节点链表,单调不减 // dKnot 节点 //返回值: // 索引值,从0开始,若为-1则表示查找失败 int FindKnotIndex(CPDSNurbsKnotList& listKnot, double dKnot); //根据给定的节点数值,计算B样条基函数的值 //特别说明: // (1)这是一个递归函数,需要调用iDegree-1的数据进行计算 // (2)在计算的过程中可能出现分母为0/0的情况,此处我们定义0/0=0 //输入参数: // listKnot 节点链表 // dKnot 给定的节点数值 // iIndex dKnot所在的索引值 // iDegree NURBS曲线的次数,一般等于3,如果只有三个控制点,那么只能生成2次曲线 //返回值: // B样条基函数的值 double CalculateKnot(CPDSNurbsKnotList& listKnot, double dKnot, int iIndex, int iDegree); //根据给定的节点数值,计算NURBS曲线上的点坐标 //输入参数: // listCtrlPoint 控制点链 // listKnot 节点链表 // dKnot 给定的节点数值 // iDegree 样条曲线的次数 //返回值: // 计算出的点位置 CPDSNurbsPoint CalculateNurbsPoint(CPDSNurbsPointList& listCtrlPoint, CPDSNurbsKnotList& listKnot, double dKnot, int iDegree); //根据给定的两个节点,计算弦高 //输入参数: // listCtrlPoint 控制点链 // listKnot 节点链表 // ptPrevNurbsPoint 前一个点的坐标 // dPrevKnot 前一个点的节点 // ptNextNurbsPoint 后一个点的坐标 // dNextKnot 后一个点的节点 // iDegree 样条曲线的次数 //返回值: // 弦高 double CalculateChordHeight( CPDSNurbsPointList& listCtrlPoint, CPDSNurbsKnotList& listKnot, CPDSNurbsPoint ptPrevNurbsPoint, double dPrevKnot, CPDSNurbsPoint ptNextNurbsPoint, double dNextKnot, int iDegree); //根据给定的数量计算等分后的节点 //特别说明: // (1)若dPrevKnot=dNextKnot,那么输出链表为空 //输入参数: // dPrevKnot 前一个节点 // dNextKnot 后一个节点 // iCount 给定节点之间的点数量,若=2,表示有2个点,输出链表为{dPrevKnot,节点1,节点2,dNextKnot} //输入参数: // listDivideKnot 分割后的节点链表,包括dPrevKnot与dNextKnot void CalculateDivideKnot(double dPrevKnot, double dNextKnot, int iCount, CPDSNurbsKnotList& listDivideKnot); //计算两个节点之间的实际点链 //特别说明: // (1)若dPrevKnot=dNextKnot,那么输出链表只有一个点 //输入参数: // listCtrlPoint 控制点链 // listKnot 节点链表 // dPrevKnot 前一个节点 // dNextKnot 后一个节点 // iDegree 样条曲线的次数 // dMaxError 最大误差值,即:每条线段的弦高不能超过该值 //输出参数: // listOutputPoint 实际点链,包含dPrevKnot与dNextKnot对应的点 void CalculateNurbsPoint_BetweenTwoKnot( CPDSNurbsPointList& listCtrlPoint, CPDSNurbsKnotList& listKnot, double dPrevKnot, double dNextKnot, int iDegree, double dMaxError, CPDSNurbsPointList& listOutputPoint); //根据等分后的节点链表计算实际点链 //输入参数: // listCtrlPoint 控制点链 // listKnot 节点链表 // listDivideKnot 等分后的节点链表 // iDegree 样条曲线的次数 // dMaxError 最大误差值,即:每条线段的弦高不能超过该值 //输出参数: // listOutputPoint 实际点链,如果计算成功,则不为空,如果计算失败,该链表为空 void CalculateNurbsPoint_UseDivideKnot( CPDSNurbsPointList& listCtrlPoint, CPDSNurbsKnotList& listKnot, CPDSNurbsKnotList& listDivideKnot, int iDegree, double dMaxError, CPDSNurbsPointList& listOutputPoint); //计算中点 //输入参数: // ptPrevNurbsPoint 前一个点,将其weight去掉之后再求解 // ptNextNurbsPoint 后一个点,将其weight去掉之后再求解 //返回值: // 中点,它的weight=1 CPDSNurbsPoint GetMiddleNurbsPoint(CPDSNurbsPoint ptPrevNurbsPoint, CPDSNurbsPoint ptNextNurbsPoint); //计算两个点之间的距离 //输入参数: // ptPrevNurbsPoint 前一个点,将其weight去掉之后再求解 // ptNextNurbsPoint 后一个点,将其weight去掉之后再求解 //返回值: // 距离 double TwoNurbsPointLength(CPDSNurbsPoint ptPrevNurbsPoint, CPDSNurbsPoint ptNextNurbsPoint); //根据给定点在线段上的参数,计算该点 CPDSNurbsPoint ParamToNurbsPoint(CPDSNurbsPoint ptPrevNurbsPoint, CPDSNurbsPoint ptNextNurbsPoint, double dParam); //查找链表中的哪个点与给定点最近,返回其索引值 //输入参数: // listPoint 链表 // ptSelPoint 给定点 //输出参数: // dMinDistance 最近距离 //返回值: // 索引值,从0开始 int FindNearestIndex(CList& listPoint, CPoint ptSelPoint, double& dMinDistance); //以累加弦长的方式生成节点 //输入参数: // listDataPoint 型值点,至少3个点 //输出参数: // listUniqueKnot 不重复的节点链表 // listKnot 重复的节点链表 void GenerateKnot_ChordLength(CPDSNurbsPointList& listDataPoint, CPDSNurbsKnotList& listUniqueKnot, CPDSNurbsKnotList& listKnot); //计算垂足在矢量上的参数值 //输入参数: // ptNurbsPointS 矢量S // ptNurbsPointE 矢量E // ptNurbsPoint1 给定点 //返回值: // 垂足点在矢量上的参数,如果SE点重合则直接返回0 double GetNurbsPointVerticalParam(CPDSNurbsPoint ptNurbsPointS, CPDSNurbsPoint ptNurbsPointE, CPDSNurbsPoint ptNurbsPoint1); //获取给定点到链表的距离 //输入参数: // ptNurbsPoint1 给定点 // listNurbsPoint 链表 //返回值: // 距离 double DistanceOfNurbsPointPloyline(CPDSNurbsPoint ptNurbsPoint1, CPDSNurbsPointList& listNurbsPoint); //查找链表中的哪个点与给定点最近 //输入参数: // ptNurbsPoint1 给定点 // listNurbsPoint 链表 //输出参数: // dDistance 最近距离 //返回值: // 最近点 CPDSNurbsPoint FindNearestNurbsPoint(CPDSNurbsPoint ptNurbsPoint1, CPDSNurbsPointList& listNurbsPoint, double& dDistance); //判断新链表与旧链表的距离是否小于误差值 //输入参数: // listOldPoint 旧的链表 // listNewPoint 新的链表 // dMaxError 最大误差值 //返回值: // =true 新链表的每个点,到旧链表的距离都小于或者等于误差值,=false 某个点的距离大于误差值 BOOL DistanceSmallerThanError(CList& listOldPoint, CList& listNewPoint, double dMaxError); //根据给定的控制点链与节点链计算实际点链,并且实际点链与旧点链的距离小于误差 //输入参数: // listCtrlPoint 控制点链 // listKnot 节点链表 // iSelIndex 给定的索引值,从0开始,从该索引值开始,向前和向后逐段计算,并比较偏差,该索引值附近的偏差是最大的 // dSplineError 计算NURBS曲线的最大误差值 // dMaxError 实际点链与旧点链的最大误差值 // listOldPoint 旧点链 //返回值: // =true 计算成功,每个点都小于等于误差值,=false 计算失败,某个点的距离大于误差值 BOOL Calculate_CtrlPoint_SmallerThanError(CPDSNurbsPointList& listCtrlPoint, CPDSNurbsKnotList& listKnot, int iSelIndex, double dSplineError, double dMaxError, CList& listOldPoint); }; #endif // _PDS_NURBS_H_