c语言课程设计台球厅系统,Monstarrr

一、概述

本系统拟完成一个图形系统,对多种常见图形进行基本操作

系统功能

二维图形的输入:可输入或全部清除不同颜色的直线、矩形、圆、椭圆、多边形、曲线、铅笔工具二维图形的编辑:对于直线、矩形、圆、椭圆、多边形、曲线,可以通过鼠标拖拽标出的控制顶点来进行编辑二维图形的剪裁:可通过拖拽编辑矩形的剪裁窗口对当前直线进行剪裁二维图形的变换:在直线、矩形、圆、椭圆、多边形、曲线内部可通过鼠标拖拽进行平移,通过按钮进行左右旋转和翻转,以及缩放二维图形的存储:可将图形存储在选择的路径下三维模型的显示:可选择加载OFF文件,显示对应的三维模型,并通过鼠标拖动转换视角

环境说明

IDE:Qt Creator 4.8.0Qt版本:Qt 5.4.0 (mingw491_32)开发语言:C/C++Debuggers:GNU gdb 7.8 for MinGW 4.9.1 32bit

编译运行说明

二、算法介绍系统包括的算法主要是二维图形的操作、三维模型的显示和用户操作的响应。

2.1 二维图形2.1.1 基本图形对于各种图形,虽然其具体的实现细节有所不同(主要体现在图形的绘制),但它们都有一些公共的属性和操作,比如计算图形的范围和中心点的位置,以及图形平移、旋转、缩放操作。这些属性和方法的算法是相同的。

1. 图形范围对于直线、矩形、圆、椭圆、多边形、曲线,遍历其控制点的值,获得minX、maxX、minY、maxY。上述图形绘制后形成的所有点,不会有点的x值不在[minX, maxX]或y值不在[minY, maxY]中。四个值可以确定图形的范围,同时用于边框的绘制。

2. 中心点中心点位置为(centerX, centerY),其中centerX = (minX + maxX) / 2,centerY = (minY + maxY) / 2。中心点可用做旋转、缩放的基准点。

3. 平移图形的绘制依赖于控制点,因此只要更新每个控制点的信息即可。在图形平移方法中,传入的参数为x轴方向移动距离的值tx和y轴方向移动距离的值ty。对于(x, y)经平移(tx, ty)向量后的坐标为(x + tx, y + ty)。

4. 旋转图形旋转传入的参数为角度数θ。控制点(x, y)顺时针旋转θ后,是以中心点(centerX, centerY)为基准的,坐标变为(centerX + (x - centerX) cosθ - (y - centerY) sinθ, centerY + (x - centerX) sinθ + (y - centerY) cosθ )。

5. 缩放图形缩放传入的参数为在x方向上缩放比例为sx、在y方向上缩放比例为sy。控制点(x, y) 在x方向上缩放比例为sx、在y方向上缩放比例为sy后,坐标变为(x sx + centerX (1 - sx), y sy + centerY (1 - sy))。

2.1.2 直线对于直线,只需要确定两个顶点的坐标作为控制点。直线生成有DDA算法、中点画线算法和Bresenham算法。

1. 线生成DDA算法线生成的DDA算法,主要是在x和y中选择“变化得快”的方向,在这个方向上选取等距的一个个值,计算出每个值对应的另一方向的值。介于x和y的值都是整数,因此每次的“增量”至少为1,计算得到的另一个值往往不是整数,是需要四舍五入的,同时后者也可能是不精确的。如果选择“变化得慢”的方向作为基准,则每次迭代,另一方向上的变化幅度就会大于1,可以想象这种算法下点会比较“稀疏”,不如“稠密”的点组成的线精密。

具体来说,当直线的斜率(m)的绝对值大于1时,就是所谓的y轴“变化得快”,选取y轴进行取样,当斜率绝对值小于1时则要选取x轴。或者在实现上,可以直接比较两个顶点x轴上和y轴上的差值的绝对值,在差值绝对值较大的方向上取样,计算差值较小的方向上的取值。确定方向后可以得到两个方向上每次取样的增量,结果应该是取样方向上增量为1,计算方向上增量为[-1, 1]之间的值(绝对值为min( |m|, |1/m|))。

迭代计算出直线上每个点的位置。由于c++语言的特性,取值可以是加0.5再取整。部分伪代码如下:

dx=x2-x1; dy=y2-y1; e=(fabs(dx)>fabs(dy))?fabs(dx):fabs(dy); dx/=e; dy/=e;for(int i=1;i<=e;i++) { SetPixel((int)(x+0.5), (int)(y+0.5)); x+=dx; y+=dy; }

2. 中点画线算法根据直线的两个点,得到直线的方程f(x, y) = a*x + b*y +c。取样方向的选择同DDA算法,下面讨论在x轴上取样、斜率为正的情况,y轴同理。

对于第i个选择的像素(xi ,yi),其下一位置的候选像素点为(xi+1, yi)和(xi+1, yi+1),取其中点(xi+1, y+1/2)代入直线方程,计算出来的决策参数为d = a * (xi+1) + b * (yi+1/2) +c。当d < 0时,中点是在直线的下方的,则靠上位置的(xi+1, yi+1)更靠近直线,如果d <= 0则选取(xi+1, yi)作为下个像素点。

根据上述方法进行迭代,至到达顶点。

3. 直线生成的Bresenham算法Bresenham算法中对于两个候选的“下一个”像素点的选取是与中点画线算法相同的。△y和△x分别为线段端点的垂直和水平偏离量(整数)在第k步,决策参数为pk = 2△y • xk - 2△x • yk +c,第k+1步的决策参数为pk+1 = 2△y • xk+1 - 2△x • yk+1 +c,根据决策参数的符号来决定像素的选取。

4. 直线的裁剪 - Cohen-Sutherland算法根据如图所示的方法对直线段的两个端点进行编码,得到两个编码结果进行比较。完全在窗口边界内的线段,两端点区域码均为0000;完全在裁剪矩形外的线段,对两个端点区域码进行逻辑与操作,结果不为0000;不能确定完全在窗口内外的线段,进行求交运算,求出线段与裁剪框的交点坐标,作为线段的新端点坐标。

2.1.3 矩形矩形由四个点首尾相连而成。事实上,只需要对角线的两个控制点,就可以组合出四个顶点的信息。矩形的绘制利用2.1.2中实现的直线生成算法,绘制出四条直线。

2.1.4 圆圆可以根据两个控制点形成的矩形来框定,选取了两个控制点x轴、y轴上差值较大的一个作为圆的直径。圆的函数为f(x, y) = x^2 + y2^2 - r^2。其画法可以看作是下述椭圆的特例,不再具体描述。只不过因为对称性,只需要画出1/8的部分,即只需要画出区域1,且区域1和区域2的分界点就是x = y的时候。

2.1.5 椭圆椭圆可以依靠两个控制点形成的一个矩形来规定范围,生成的椭圆内切在其中。椭圆的圆心就是中心位置(centerX, centerY),x轴上的直径ra和y轴上的直径rb分别是两点x轴、y轴上差值的。

根据图形平移的理论,可以以原点为圆心计算出椭圆上的点(x, y),再变换成(x + centerX, y + centerY)。因为椭圆的对称性,可以在以原点为圆心的基础上,只进行第一象限的计算。椭圆的绘制采用中点椭圆生成算法。

中点椭圆生成算法如图所示,定义椭圆函数f(x, y) = ry^2 * x^2 + rx^2 * y^2 - rx^2 * ry^2。以圆切线斜率绝对值为1作为划分,第一象限可以分为区域1和区域2。实际上,第一象限的切线斜率为负数,斜率dy/dx = -2ry^2 * x / (rx^2 * y),因此区域1进入区域2的条件是2ry^2 * x >= rx^2 * y。区域1中x轴“变化得快”,在x轴上取样,区域2则在y轴上取样。从(0, ry)或(rx, 0)开始,进行迭代,计算出椭圆上每个点的位置。下面指出区域1中的计算方法,区域2中同理。

椭圆上的点的决策参数的初始值为p0 = ry^2 - rx^2 * ry + rx^2 / 4。假设第k次选择的像素为(xk, yk),为确定下一次选择的像素,计算x(k+1)位置两个候选像素点的中点,即(xk+1, yk)和(xk+1, yk-1)的中点(xk+1, yk-1/2),将其代入椭圆函数求出决策参数。

若p1k < 0,则这个中点在圆内,(xk+1, yk)更接近真实的圆;若p1k >= 0,中点在不在圆内,选取(xk+1, yk-1)作为下一个像素点。迭代至循环结束,最后将第一象限的每个像素点映射到其余三个象限,再从原点将圆心移动到原本的中心位置。

2.1.6 多边形多边形的生成也以直线生成为基础,利用直线的绘制将各个多边形的顶点作为控制点首尾相连起来。在实现上,多边形还需要一个额外的变量来记录控制点是否输入完全(首尾共点)。

2.1.7 曲线曲线的绘制需要若干控制点,通过计算各个像素点的位置,将所有控制点按照顺序平滑地连接起来。曲线分为Bezier曲线和B样条曲线。

1. Bezier曲线给定Pk=(xk,yk,zk),(k=0,1,2,…,n)共n+1个控制点,这些点混合产生位置向量BEZk,n(u)是Bernstein多项式,BEZi,n(u)=C(n,i)ui(1-u)n-i,其中C(n,i)=n!/[i!(n-i)!] (i=0,1,…,n) 。利用Bernstein基函数的降(升)阶公式,可使用递归计算得出Bézier曲线上点的坐标位置:BEZk,n(u)=(1-u)BEZk,n-1(u)+uBEZk-1,n-1(u),其中BEZk,k(u)=uk, BEZ0,k(u)=(1-u)k。

2. B样条曲线给定n+1个控制顶点{Pi}(i=0,1,…,n),P0P1…Pn为控制多边形 n+k+2个参数节点向量:Un,k={ui|i=0,1,…,n+k+1,ui≤ui+1}。称如下形式的参数曲线P(u)为k+1阶(k次)B样条曲线:其中,Bi,k+1(u)为k+1阶(k次)B样条基函数。Bi,k+1(u)双下标中下标k+1表示k+1阶(k次)数、下标i表示序号。

2.1.8 填充区域填充区域需要先有一个像素包围的封闭图形框架,然后计算出其内的所有像素点的位置。算法有扫描填充图元生成和区域填充图元生成。

1. 扫描填充图元生成扫描填充的核心是“扫描”,一般从多边形的顶点出发,移动一条横越图形的扫描线,每次求交点,从左至右配对存储这些交点。扫描线每与两条线相交,这条线的交点表上就会有添加两个交点。需要处理共享顶点两条边位于扫描线同侧和异侧的情况。交点表的存储可以利用活化边表、排除不必要的求交测试的有序边表。

2. 区域填充图元生成区域填充需要从一个连通的图形内的一个种子点开始,扩展到整个区域,直至遇到了边界。该算法比较适合实现“油漆桶”类似的功能。

由于区域填充有着“递归”的思想,递归的结束条件至关重要,即对于“遇到边界”的判断。在此用到了图形4连通和8连通的内容,对于像素4连通的区域,其边界像素只需要是8连通的,也可以是过约束的4连通边界;而8连通的区域,边界像素只能是4连通的,如果边界是8连通,则欠约束,内部的点会通过对角线位置上没有填充像素的边界的空缺,泛滥到边界之外,造成全局的填充。

此外,像素4连通区域定义的图形相对简单,8连通区域可以定义更复杂的图形,比如两个像素间只通过对角线形成连通的图形。

2.1.9 铅笔工具铅笔工具的控制点是鼠标移动的轨迹,每个点都是控制点需要记录。为了保持轨迹的连贯性,可以在每两个距离很小的相邻点间用直线连接。

2.2 三维模型取读OFF文件并显示其表示的三维模型,重点是在了解OFF文件存储的格式以及其表示的内容。

OFF文件第一行为“off”字符串,第二行是三个整数,分别表示了这个模型的点数量numV、面数量numF、边数量numE。接下来的numV行是点的信息,每行都是每个点的x、y、z值,值的定义域为[-1,1]的浮点数,同时也确定了点的编号(从0开始)。然后是numF行的面信息,每行第一个整数表示这个面拥有的点的数量,接下来的几个整数是点的编号。

在三维模型中,需要将面打印出来,也就是将每个面的顶点按顺序输出。

2.3 主界面主界面是实现用户各种操作的接口,主要对于不同的鼠标事件进行响应。

三、实现本系统采用面向对象的设计方法,将类分为“二维图形”、“三维模型窗口”、“主界面”三种。

3.1 二维图形3.1.1 基本图形 Shape

基本图形记录的信息有:

图形类型:ShapeType typeenum ShapeType { BLANK, _LINE, _CURVE, _RECT, _CIRCLE, _OVAL, _POLY, _PEN, _FILL};图形控制点:QVector\<QPoint\*> points图形中点信息:int centerX, centerY图形范围:int minX, maxX, minY, maxY图形的颜色和绘制画笔风格:QRgb rgb, QPen pen

基本图形共有的相同实现的方法有:

构造函数:Shape(QVector\<QPoint\*> points, ShapeType type, QRgb rgb, QPen pen);改变某一控制点point为某值a:void setPoint(QPoint* point, QPoint a)判断a是否在图形某一控制点point上,允许有1个单位的误差:bool isAroundPoint(QPoint* point, QPoint a);着重打印出控制点(x, y)以便鼠标拖拽:void paintVertex(QPainter& p, int x, int y);移动某一控制点point改变(dx, dy),算法见2.1.1:void movePoint(QPoint* point, int dx, int dy);旋转某一控制点point弧长为arg,算法见2.1.1:void rotatePoint(QPoint* point, double arg);缩放某一控制点point,x轴上比例为sx,y轴上比例为sy,算法见2.1.1:void zoomPoint(QPoint* point, double sx, double sy);翻转某一控制点point,isV为true代表是竖直方向,否则为水平翻转,只需要让point的值以(centerX, centerY)作对称求值即可:void flipPoint(QPoint* point, bool isV);

图形中还有一些共有的操作,具体实现有所不同,可以设置为虚函数:

设置当前最后一个控制点为a,只有两个控制点的图形只需要更新points.last(),多边形和曲线另外实现:void setPoint(QPoint* point, QPoint a);判断当前的位置a是否是在某一控制点上并返回,基本思想是遍历控制点,调用isAroundPoint():QPoint* isAround(QPoint a);当前的位置a是否在图形内,主要根据范围的四个值来判断:bool isInside(QPoint a);更新图形的信息,如中心点、范围:void refreshData();绘制图形:void paintShape(QPainter& p, QImage* image, bool isSave);绘制当前图形的框架,主要绘制图形范围形成的矩形,加重控制点的绘制:void paintFrame(QPainter& p);移动图形增量为(dx, dy),基本思想是对每个控制点调用movePoint():void move(int dx, int dy);旋转图形弧长为arg,基本思想是对每个控制点调用rotatePoint():void rotate(double arg);缩放图形x和y轴上的比例分别为sx和sy,基本思想是对每个控制点调用zoomPoint():void zoom(double sx, double sy);翻转图形,对控制点进行flipPoint(),对称的矩形、圆、椭圆不用翻转:void flip(bool isV);裁剪图形(直线),保留在两点形成的矩形内的部分:void cutShape(QPoint* cutStartPos, QPoint* cutEndPos);

3.1.2 直线 Line直线的特征体现在只有(x1, y1)和(x2, y2)两个控制点,属性的更新也是在这两个点的基础上进行。

直线的剪裁首先确定裁剪框的大小,再对两个控制点进行编码,即调用int getCode(int x, int y, int minXW, int maxXW, int minYW, int maxYW)。获得的编码的四位从高到低依次表示是否在裁剪框的上下左右位置。如果编码不为0,则表示控制点在裁剪框外,需要根据直线的方程确定出交点。例如,如果第一个控制点在裁剪框的左侧,则可以计算出新的x = minXW时的y值。

int newY = (int) points.first()->y() + m * (minXW - points.first()->x()) + 0.5;points.first()->setY(newY);points.first()->setX(minXW);

对于直线来说,可以改变它的两个端点,“在直线内”的定义是在它的外接矩形内。

3.1.3 矩形 Rect矩形主要注意四个顶点的顺序,只有两个控制点,这两个控制点间不是相连的。在更新控制点时,需要改变的是第二个控制点的值,同时需要重新计算其余一条对角线上的两个顶点的位置。绘制时需要新建直线对象,调用直线的绘制函数。

3.1.4 圆Circle圆也是由两个控制点形成的,需要注意的是半径的设置,同时也是外接正方形四个顶点统一的问题。计算出两个控制点间的差值,选择较大的一个作为直径,为了方便后续的计算,在refreshData()中也更新控制点的值。下面给出了如果x轴方向差值较大的更新情况。

int len = fabs(points[0]->x() - points[2]->x());int width = fabs(points[0]->y() - points[2]->y());if (len > width){ if (points[0]->y() < points[2]->y()) points[2]->setY(points[0]->y() + len); else points[2]->setY(points[0]->y() - len);}

因为并非所有圆上的像素信息都保存在图形信息内,为减少判断的计算量,在外接正方形内,都算是“在圆内”,可以进行拖动等操作。此外,圆不需要实现旋转和翻转。

3.1.5 椭圆 Oval椭圆也是根据外接矩形来确定各个参数的,绘制的具体算法在2.15中已经指出。对于平移和对称特性,在实现时,使用了void ovalPoints(QPainter& p,QImage* image, bool isSave, int centerX, int centerY, int x, int y)函数,对于计算出的第一象限的(x, y),绘制(x + centerX, y + centerY)、(-x + centerX, y + centerY)、(x + centerX, -y + centerY)、(-x + centerX, -y + centerY)。

在外接矩形内就算做“在椭圆内”。此外,椭圆是不需要进行翻转的。

3.1.6 多边形 Poly多边形的一组控制点作为多边形的顶点,每一个都是用户自定义的,需要按照顺序用直线连接起来。较之于矩形,多边形会有“封闭”与否的状态,实时绘制时,在未封闭的时候,最后一个控制点是不会与第一个控制点相连的,只有当用户最后点击起始点时才会封闭。在实现上,为多边形设置一个bool变量isEnd来记录这个图形的绘制是否完成。在每次setPoint时,是在新增控制点,判断工作交由refreshData()处理。在更新信息时,判断最后一个点是否是在起始点附近,如果是,则这个多边形绘制完成,并且将最后一个顶点的指针指向第一个顶点。同时要求多边形的顶点个数不能少于3。

多边形只有在绘制完成后才会描绘出边框。在多边形各个控制点进行平移、旋转、缩放时,需要注意,最后一个控制点其实就是第一个控制点,不要再进行第二次的变换。

3.1.7 曲线 Curve曲线对于控制点输入的处理与多边形类似,只不过再此设定曲线输入结束的标志为当输入的顶点与最后一个控制点重合(在附近)时,更新后的最后一个控制点指针其实是与倒数第二个相同的。在对各个控制点进行平移、旋转、缩放时,也需要忽略最后一个控制点。

3.1.8 填充区域 Fill对于“油漆桶”功能,用户只需要输入一个点的位置,采取区域填充的方式,递归不断加入需要填充的像素的位置。为了避免填充区域颜色和边界颜色相同导致的过早停止填充问题,只要像素是白色qRgb(255, 255, 255)就不是边界,不是边界则继续递归。

对于“实心”图形,在绘制边框的时候已知边界,在绘制同时填充内部区域。

3.1.9 铅笔工具 Pen为了确保连续,在点之间用直线进行相连。铅笔工具与多边形类似,只是不需要对于封闭性有所判断,也没有编辑、平移、旋转、缩放的功能。

3.2 三维模型 Window三维模型的点信息用MyVertex结构描述。面信息定义为MyFace,其中numV表示面上的点数,indexV记录各个点的编号。

struct MyVertex{ GLfloat vx; GLfloat vy; GLfloat vz; MyVertex() {} MyVertex(GLfloat x, GLfloat y, GLfloat z) : vx(x), vy(y), vz(z) {}};struct MyFace{ int numV; QVector<int> indexV;};

在主界面上可以再打开一个用于显示三维模型的窗口。这个窗口定义为Window类,包含了的信息有:

点、面、边的个数:int numVertices, numFaces, numEdges

所有点信息:QVector\<MyVertex> vertices

所有面信息:QVector\<MyFace> faces

用于鼠标控制模型转动的记录信息:QPoint* startPos、QPoint* endPos、double rotateX、double rotateY

类中实现的方法主要是:

初始化:void initializeGL()

绘制模型:void paintGL()

鼠标事件:void mousePressEvent(QMouseEvent *ev)、void mouseMoveEvent(QMouseEvent *ev)、void mouseReleaseEvent(QMouseEvent *ev)

3.2.1 读取文件QString fileName = QFileDialog::getOpenFileName(this, tr(“Open Config”), “”, tr(“OFF Files (*.off)”));可以获取到选择的off文件的文件名,ifstream inFile(fileNameChar, ios::in);获得文件流,进行对象的初始化。

3.2.2 绘制模型 paintGL()glBegin(GL_POLYGON)与glEnd()间绘制模型,对每个点vij进行glVertex3f(vij.vx, vij.vy, vij.vz)的绘制。为了显示出模型内部构造,利用glCollor3f()给顶点设置几种不同颜色。

在绘制的最后,可能需要对模型进行旋转,旋转的角度保存在rotateX和rotateY中,根据鼠标事件实时更新。

glRotated(rotateX, 0.0, -1.0, 0.0);glRotated(rotateY, -1.0, 0.0, 0.0);

3.2.3 鼠标事件记录鼠标位置的改变,映射成旋转的角度。

double x = (double)(endPos->x()-startPos->x())/this->width()*60;double y = (double)(endPos->y()-startPos->y())/this->height()*60;

3.3 主界面 MainWindow主界面是实现用户各种操作的接口,主要对于不同的鼠标事件进行响应。

主界面中包含的信息有:

所有图形的存储:QVector\<Shape\*> shapes当前表示的图形类型:ShapeType curShapeType当前绘制中或绘制完成可编辑的图形:Shape* curShape当前画笔的类型:QPen curPen用于存储图像的信息:QImage* image、QRgb rgb用于鼠标移动的记录信息:QPoint* startPos、QPoint* endPos用于记录裁剪信息:QPoint* cutStartPos、QPoint* cutEndPos

可以进行的操作:

界面的绘制:void paintEvent(QPaintEvent *ev)鼠标事件:void mousePressEvent(QMouseEvent *ev)、void mouseMoveEvent(QMouseEvent *ev)、void mouseReleaseEvent(QMouseEvent *ev)各种按钮的响应,包括按钮和菜单

3.3.1 图形绘制图形的绘制首先需要建立画笔,设置画笔的风格,将这个画笔作为参数传到各个图形中去。

QPainter p(this);p.setPen(curPen);

绘制需要遍历shapes,调用每个shape的paintShape()。如果curShape是完成绘制可编辑的,则可以绘制出其框架,调用curShape->paintFrame()。

3.3.2 鼠标事件在鼠标按下事件中,分为几类操作:

新建一个curShapeType类型的图形,需要更新curShape,设置当前图形为未完成的继续绘制当前图形,需要对当前图形的类型作出判断,只有多边形和曲线可以继续绘制,更新当前图形的信息编辑顶点,需要使得ev->pos()在图形控制点周围,开始记录这个控制点的变更移动当前图形,需要判断ev->pos()是否在图形内,只有在图形内才可以更新startPos开始记录移动的位置,并把图像状态标记为isMove = true进行裁剪,需要是在当前可编辑图形为直线的情况下

鼠标移动事件是在鼠标按下事件之后、鼠标释放之前进行的,也可以不进行这个阶段:

继续绘制图形,更新图形的控制点改变图形某一个控制点更新移动信息中的endPos编辑裁剪窗口

鼠标释放事件对当前鼠标行为做收尾工作:

完成绘制,将当前图形更新为可编辑状态完成编辑完成移动,将startPos、endPos清空,isMove设置为false完成剪裁,调用直线的剪裁方法,再清空剪裁信息

在鼠标事件的所有操作进行之后,使用update()来调用paintEvent()。

3.3.3 设置更改

通过点击图形来更改curShapeType的类型

通过点击调色板更改QPen的颜色

3.3.4 图形存储

通过点击按钮,保存当前图形image

saveFileName = QFileDialog::getSaveFileName(this, tr("Save Image"), "", tr("JPG Files (*.jpg)")); //选择路径image->save(saveFileName);

四、界面展示主界面

二维图形(直线、矩形、圆、椭圆、多边形、曲线、填充图形)的输入

二维图形的编辑

二维图形的平移、旋转和缩放

二维图形的保存结果

三维图形的显示

五、参考文献

《计算机图形学教程》

Qt画图小项目 https://blog.csdn.net/qianqin_2014/article/details/51236712

Docs/.NET/.NET API浏览器/System.Drawing/Graphicshttps://docs.microsoft.com/zh-cn/dotnet/api/system.drawing.graphics?view=netframework-4.7.2

圆和椭圆算法https://blog.csdn.net/qq_32307691/article/details/73437967

B样条曲线 https://blog.csdn.net/Jurbo/article/details/75125663

Bezier曲线 https://blog.csdn.net/jurbo/article/details/75069054

Qt5.7.1中使用QImage保存成JPG文件到本地 https://blog.csdn.net/ZefinNg/article/details/81150683

OFF文件格式 https://www.cnblogs.com/youthlion/archive/2012/02/04/2337790.html

易百教程 3D图形 https://www.yiibai.com/jogl/jogl_3d_graphics.html

5

留言

2020-09-25 09:54:16

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值