您的位置:首页 > 运维架构

OpenGL学习笔记3:变换,矩阵,更多对象

2017-04-25 11:34 393 查看


变换

进行图形开发,3D图形数学基础是必不可少,限于其专业性,OpenGL笔记里不做详细赘述。但是对相关术语及其在OpenGL对应的内容还是需要一定的了解。


基础


向量

点乘 

两个(三分量)单位向量之间的点乘运算将得到一个标量(只有一个值),它表示两个向量之间的夹角。要进行这种运算,这两个向量必须为单位长度,而返回的结果将在-1.0到1.0之间。这个数字实际上就是这两个向量之间夹角的余弦值。 

我们可以使用m3dDotProduct3函数来实际获得两个向量之间的点乘结果。
float m3dDotProduct3(const M3DVector3f u,const M3DVector3f v);
1
1

m3dGetAngleBetweenVectors3返回这个角的弧度制。
float m3dGetAngleBetweenVectors3 (const M3DVector3f u,const M3DVector3f v);
1
1

叉乘 

要进行叉乘,两个向量都不必为单位向量。 

两个向量之间叉乘所得的结果是另外一个向量,这个新向量与原来两个向量定义的平面垂直。 

m3dCrossProduct3对两个向量进行叉乘并返回运算得到的结果向量。
void m3dCrossproduct3(M3DVector3f result,const M3DVector3f u,const M3DVector3f v);
1
1


矩阵

矩阵是一个二维数组,一个矩阵只有一行或者一列也是合法的。 

在我们进行3D程序设计工作时,我们将使用的几乎全部是3X3和4X4矩阵。math3d库中有这两种矩阵数据类型:
typedef float M3DMatrix33f[9];
typedef float M3DMatrix44f[16];
1
2
1
2


变换

OpenGL变换数据概览
变换应用
视图制定观察者或照相机的位置
模型在场景中移动物体
模型视图描述视图和模型变换的二元性
投影改变视窗体 的大小或重新设置它的形状
视口这是一种伪变换,只是对窗口上的最终输出进行缩放


变换 应用

视图 制定观察者或照相机的位置 

模型 在场景中移动物体 

模型视图 描述视图和模型变换的二元性 

投影 改变视窗体 的大小或重新设置它的形状 

视口 这是一种伪变换,只是对窗口上的最终输出进行缩放


视觉坐标

笛卡尔坐标系:从观察者的角度来看,x轴和y轴的正方向分别指向右方和上方。z轴的正方向从原点指向使用者,而z轴的负方向则从观察者指向屏幕内部。 

当我们利用OpenGL进行3D绘制时,就会使用笛卡尔坐标系。如果不进行任何变换,那么使用的坐标系将与刚刚描述的视觉坐标系相同。


视图变换

视图变换允许我们把观察点放在所希望的任何位置,并允许在任何方向上观察场景。确定视图变换就像在场景中放置照相机并让它指向某个方向。


模型变换

模型变换用于操纵模型和其中的特定对象。这些变换将对象移动到需要的位置,然后再对它们进行旋转和缩放。


投影变换

投影变换将在模型视图变换之后应用到顶点上,它将指定一个完成的场景(所有模型变换都已完成)是如何投影到屏幕上的最终图像。 

正投影:所有多边形都是精确地按照指定的相对大小来在屏幕上绘制的。 

透视投影:透视投影的特点是透视缩短,这种特性使得远处的物体看起来比进出同样大小的物体更小一些。


视口变换

当所有变换完成后,就得到了一个场景的二维投影,它将被映射到屏幕上某处的窗口上。这种到物理创口标的映射是我们最后要做的变换,称为视口变换。


矩阵


模型视图矩阵

模型视图矩阵是一个4X4矩阵,它表示一个变换后的坐标系,我们可以用来放置对象和确定对象的方向。一个包含单个顶点数据的矩阵乘以模型视图矩阵后得到新的视觉坐标。


矩阵构造

OpenGL并不是将一个4X4矩阵表示为一个浮点值的二维数组,而是将它表示为一个由16个浮点值组成的单个数组。 


 

前三纵列分别对应x轴,y轴,z轴上的方向。 

如果有一个包含一个不同坐标系的位置和方向的4X4矩阵,然后用一个表示原来坐标系的向量(表示为一个列矩阵或向量)乘以这个矩阵,得到的结果是一个转换到新坐标系下的新向量。这就意味着,空间中任意位置和任何想要的方向都可以由一个4X4矩阵唯一确定,并且如果用一个对象的所有向量乘以这个矩阵,那么我们就将整个对象变换到了空间中的给定位置和方向。 

单位矩阵 

单位矩阵中除了对角线上的一组元素之外,其他元素均为0。将一个向量乘以一个单位矩阵,就相当于用这个向量乘以1,不会发生任何改变。 
平移 

我们可以调用math3d库中的m3dTranslationMatrix44函数来使用变换矩阵。
void m3dTranslationMatrix44(M3DMatrix44f m,float x,float y,float z);
1
1

旋转
m3dRotationMatrix44(M3DMatrix44f m,float angle,float x,float y,float z);
1
1

这个函数让我们围绕一个由x、y和z变量指定的向量来进行旋转。旋转的角度沿逆时针方向按照弧度计算,由变量angle指定。 

下面的代码创建一个旋转矩阵,可以使顶点沿着任意由(1,1,1)指定的轴旋转45度: 

m3dRotationMatrix(m3dDegToRad(45.0),1.0f,1.0f,1.0f); 

宏m3dDegToRad将角度值转换为弧度制。 
缩放
void m3dScaleMatrix44(M3DMatrix44f m,float xScale,float yScale,float zScale);
1
1

综合变换 

math3d库函数m3dMatrixMultiply44用来将两个矩阵相乘并返回运算结果。
void m3dMatrixMultiply44(M3DMatrix44f product,const M3DMatrix44f a,const M3DMatrix44f b);
1
1


运用模型视图矩阵

// Called to draw scene
void RenderScene(void)
{
// Clear the window with current clearing color
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f };

M3DMatrix44f mFinalTransform, mTranslationMatrix, mRotationMatrix;

// 平移操作
m3dTranslationMatrix44(mTranslationMatrix, xPos, yPos, 0.0f);

// 每次绘制旋转5度
static float yRot = 0.0f;
yRot += 5.0f;
m3dRotationMatrix44(mRotationMatrix, m3dDegToRad(yRot), 0.0f, 0.0f, 1.0f);

//连接平移和旋转的结果到最终矩阵
m3dMatrixMultiply44(mFinalTransform, mTranslationMatrix, mRotationMatrix);

//使用矩阵将坐标存储到平面着色器中
shaderManager.UseStockShader(GLT_SHADER_FLAT, mFinalTransform, vRed);
squareBatch.Draw();

// Perform the buffer swap
glutSwapBuffers();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28


投影

Orthographic(正交)和Perspective(透视)


正投影

我们可以使用math3d库或GLFrustum类来创建一个正投影矩阵
GLFrustum::SetOrthographic(GLfloat xMin,GLfloat xMax,GLfloat yMin,GLfloat yMax,GLfloat zMin,GLfloat zMax);
1
1


透视投影

GLFrustum::SetPerspective(float fFov,float fAspect,float fNear,float fFar);
1
1


变换管线


使用矩阵堆栈

GLMatrixStack类的构造函数允许指定堆栈的最大深度,默认的堆栈深度为64.这个矩阵堆栈在初始化时已经在堆栈中包含了单位矩阵。
GLMatrixStack::GLMatrixStack(int iStackDepth=64);
1
1

我们可以通过调用在顶部载入这个单位矩阵。
void GLMatrixStack::LoadIdentity(void);
1
1

或者可以在堆栈顶部载入任何矩阵。
void GLMatrixStack::LoadMatrix(const M3DMatrix44f m);
1
1

此外,我们可以用一个矩阵乘以矩阵堆栈的顶部矩阵,相乘得到的结果随后将存储在堆栈的顶部。
void GLMatrixStack::MultMatrix(const M3DMatrix44f);
1
1

最后,只要用GetMatrix函数就可以获得矩阵堆栈顶部的值,这个函数可以进行两次重载,以适应GLShaderManager的使用,或者仅仅是获得顶部矩阵的副本。
const M3DMatrix44f& GLMatrixStack::GetMatrix(void);
void GLMatrixStack::GetMatrix(M3DMatrix44f mMatrix);
1
2
1
2

压栈与出栈
void GLMatrixStack::PushMatrix(void);
void PushMatrix(const M3DMatrix44f mMatrix);
void PushMatrix(GLFrame& frame);
void GLMatrixStack::PopMatrix(void);
1
2
3
4
1
2
3
4

仿射变换 

GLMatrixStack类也内建了对创建旋转、平移和缩放矩阵的支持。相应的函数列出如下:
void MatrixStack::Rotate(GLfloat angle,GLfloat x,GLfloat y,GLfloat z);
void MatrixStack::Translate(GLfloat x,GLfloat y,GLfloat z);
void MatrixStack::Scale(GLfloat x,GLfloat y,GLfloat z);
1
2
3
1
2
3


管理管线

示例:
void ChangeSize(int nWidth, int nHeight)
{
glViewport(0, 0, nWidth, nHeight);

// 创建投影矩阵,并将它载入到投影矩阵堆栈中
viewFrustum.SetPerspective(35.0f, float(nWidth)/float(nHeight), 1.0f, 100.0f);
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());

// 设置变换管线以使用两个矩阵堆栈
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}

// Called to draw scene
void RenderScene(void)
{
// 颜色值
static GLfloat vFloorColor[] = { 0.0f, 1.0f, 0.0f, 1.0f};
static GLfloat vTorusColor[] = { 1.0f, 0.0f, 0.0f, 1.0f };

// 基于时间的动画
static CStopWatch   rotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;

// 清除颜色缓冲区和深度缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// 保存当前的模型视图矩阵 (单位矩阵)
modelViewMatrix.PushMatrix();

// 绘制背景
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModelViewProjectionMatrix(),
vFloorColor);
floorBatch.Draw();

// 绘制旋转的花托
modelViewMatrix.Translate(0.0f, 0.0f, -2.5f);
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(),
vTorusColor);
torusBatch.Draw();

// 保存以前的模型视图矩阵 (单位矩阵)
modelViewMatrix.PopMatrix();

// 进行缓冲区交换
glutSwapBuffers();

// 通知GLUT再进行一次同样操作
glutPostRedisplay();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53


使用照相机和角色进行移动


照相机管理

3D环境中典型的渲染循环流程 

循环{ 

保存单位矩阵 

应用照相机变换 

绘制不会移动的物体 

绘制移动的物体(角色) 

循环{ 

绘制角色几何图形 

应用角色变换 

应用照相机变换 

恢复照相机变换 



恢复单位矩阵 



GLFrame函数用来检索条件适合的照相机矩阵:
void GetCameraMatrix(M3DMatrix44f m,bool bRotationOnly=false);
1
1


光线

将一个固定光源位置变换到视觉坐标在每个成精中只需进行一次:
M3DVector4f vLightPos={0.0f,10.0f,5.0f,1.0f};
M3DVector4f VLightEyePos;
m3dTransformVector4(vLightEyePos,vLightPos,mCamera);
1
2
3
1
2
3

例如要渲染一个蓝色球体:
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(),transformPipeline,GetProjectionMatrix(),vLightEyePos,vSphereColor);
1
1


更多对象


三角形批次类

首先,我们需要为对象创建一个事件。 

GLTriangleBatch myCoolObject; 

然后通知容器最多打算使用的顶点数,开始创建网格。 

myCoolObject.BeginMesh(200); 

接着来添加三角形,AddTriangle成员函数接受一个包含3个顶点的数组,一个包含3个法线的数组,以及一个包含3个纹理坐标的数组。 

void GLTriangleBatch::AddTrangle(M3DVector3f verts[3],M3DVector3f vNorms[3],M3DVector2f vTexCoords[3]); 

当我们添加完三角形时,调用End。 

myCoolObject.End(); 

最后,调用Draw函数。 

myCoolObject.Draw();


球体

gltMakeSphere函数引用一个三角形批次、求的半径和组成球体的片段及其堆叠数量: 

void gltMakeSphere(GLTriangleBatch& sphereBatch,GLfloat fRadius,Glint iSlices,Glint iStacks);


花托

void gltMakeTorus(GLTriangleBatch& torusBatch,GLfloat majorRadius,GLfloat minorRadius,Glint numMajor,Glint numMinor);


圆柱或圆锥

void gltMakeCylinder(GLTriangleBatch& cylinderBatch,GLfloat baseRadius,GLfloat topRadius,GLfloat fLength,Glint numSlices,Glint numStacks);


圆盘

void gltMakeDisk(GLTriangleBatch& diskBatch,GLfloat innerRadius,GLfloat outerRadius,Glint nSlices,Glint nStacks);

转载请注明出处:http://blog.csdn.net/ylbs110/article/details/51760021


示例

#include "stdafx.h"
#include <GLTools.h>
#include <GLShaderManager.h>
#include <GLFrustum.h>
#include <GLBatch.h>
#include <GLFrame.h>
#include <GLMatrixStack.h>
#include <GLGeometryTransform.h>
#include <StopWatch.h>

#include <math.h>
#include <stdio.h>

#include <math.h>
#define GLUT_DISABLE_ATEXIT_HACK
#include <GLUT.H>

/*
* 当libjpeg-turbo为vs2010编译时,vs2015下静态链接libjpeg-turbo会链接出错:找不到__iob_func,
* 增加__iob_func到__acrt_iob_func的转换函数解决此问题,
* 当libjpeg-turbo用vs2015编译时,不需要此补丁文件
*/
#if _MSC_VER>=1900
#include "stdio.h"
_ACRTIMP_ALT FILE* __cdecl __acrt_iob_func(unsigned);
#ifdef __cplusplus
extern "C"
#endif
FILE* __cdecl __iob_func(unsigned i) {
return __acrt_iob_func(i);
}
#endif /* _MSC_VER>=1900 */

#define NUM_SPHERES 50
GLFrame disk[NUM_SPHERES/2];
GLFrame cylinder[NUM_SPHERES/2];

GLShaderManager     shaderManager;          // Shader Manager
GLMatrixStack       modelViewMatrix;        // Modelview Matrix
GLMatrixStack       projectionMatrix;       // Projection Matrix
GLFrustum           viewFrustum;            // View Frustum
GLGeometryTransform transformPipeline;      // Geometry Transform Pipeline

GLTriangleBatch     torusBatch;
GLBatch             floorBatch;
GLTriangleBatch     sphereBatch;
GLTriangleBatch     triangleBatch;
GLTriangleBatch     cylinderBatch;
GLTriangleBatch     diskBatch;

GLFrame             cameraFrame;

//////////////////////////////////////////////////////////////////
// This function does any needed initialization on the rendering
// context.
void SetupRC()
{
// Initialze Shader Manager
shaderManager.InitializeStockShaders();

glEnable(GL_DEPTH_TEST);

glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

// 制造花圈
gltMakeTorus(torusBatch, 0.4f, 0.15f, 30, 30);

// 制造小球
gltMakeSphere(sphereBatch, 0.3f, 26, 13);

//制造圆柱
gltMakeCylinder(cylinderBatch, 0.2f, 0.2f, 0.5f, 13, 2);

//制造圆盘
gltMakeDisk(diskBatch, 0.2f, 0.4f, 13, 3);

floorBatch.Begin(GL_LINES, 324);
for (GLfloat x = -20.0; x <= 20.0f; x += 0.5) {
floorBatch.Vertex3f(x, -0.55f, 20.0f);
floorBatch.Vertex3f(x, -0.55f, -20.0f);

floorBatch.Vertex3f(20.0f, -0.55f, x);
floorBatch.Vertex3f(-20.0f, -0.55f, x);
}
floorBatch.End();

// Randomly place the spheres
for (int i = 0; i < NUM_SPHERES; i++) {
GLfloat x = ((GLfloat)((rand() % 400) - 200) * 0.1f);
GLfloat z = ((GLfloat)((rand() % 400) - 200) * 0.1f);
if(i%2==0)
disk[i/2].SetOrigin(x, 0.0f, z);
else
cylinder[(i-1)/2].SetOrigin(x, 0.0f, z);
}

GLfloat vetts[3][3];
GLfloat vNorms[3][3];
GLfloat vTexCoords[3][2];
GLfloat angle = 0;
for (int i = 0; i < 3; i++) {
angle += M3D_2PI / 6.0f;
vetts[i][0] = float(-5 + i*0.2);
vetts[i][1] = float(sin(float(angle)));
vetts[i][2] = float(cos(float(angle)));

vNorms[i][0] = float(-5 + i*0.2);
vNorms[i][1] = float(cos(float(angle)));
vNorms[i][2] = float(sin(float(angle)));

vTexCoords[i][0] = float(-5 + i*0.2);
vTexCoords[i][1] = float(sin(float(angle)));
}
triangleBatch.BeginMesh(3);
triangleBatch.AddTriangle(vetts, vNorms, vTexCoords);
triangleBatch.End();

}

///////////////////////////////////////////////////
// Screen changes size or is initialized
void ChangeSize(int nWidth, int nHeight)
{
glViewport(0, 0, nWidth, nHeight);

// 创建投影矩阵,并将它载入到投影矩阵堆栈中
viewFrustum.SetPerspective(35.0f, float(nWidth) / float(nHeight), 1.0f, 100.0f);
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());

// 设置变换管线以使用两个矩阵堆栈
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}

// Called to draw scene
void RenderScene(void)
{
// 颜色值
static GLfloat vFloorColor[] = { 0.0f, 1.0f, 0.0f, 1.0f };
static GLfloat vTorusColor[] = { 1.0f, 0.0f, 0.0f, 1.0f };
static GLfloat vSphereColor[] = { 0.0f, 0.0f, 1.0f, 1.0f };
static GLfloat vdiskColor[] = { 0.0f, 0.5f, 0.5f, 1.0f };
static GLfloat vcylinderColor[] = { 0.5f, 0.0f, 0.5f, 1.0f };

// 基于时间的动画
static CStopWatch   rotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;

// 清除颜色缓冲区和深度缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// 保存当前模型视图矩阵 (单位矩阵)
modelViewMatrix.PushMatrix();

M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.PushMatrix(mCamera);

// 将光源位置变换到视觉坐标系
M3DVector4f vLightPos = { 0.0f, 10.0f, 5.0f, 1.0f };
M3DVector4f vLightEyePos;
m3dTransformVector4(vLightEyePos, vLightPos, mCamera);

// 绘制背景
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModelViewProjectionMatrix(),
vFloorColor);
floorBatch.Draw();

//绘制圆柱和圆盘
for (int i = 0; i < NUM_SPHERES; i++) {
modelViewMatrix.PushMatrix();
if (i % 2 == 0) {
modelViewMatrix.MultMatrix(disk[i / 2]);
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(), vLightEyePos, vdiskColor);
diskBatch.Draw();
}
else
{
modelViewMatrix.MultMatrix(cylinder[(i - 1) / 2]);
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(), vLightEyePos, vcylinderColor);
cylinderBatch.Draw();

}
modelViewMatrix.PopMatrix();
}

// 绘制旋转花托
modelViewMatrix.Translate(0.0f, 0.0f, -2.5f);

// 保存平移矩阵
modelViewMatrix.PushMatrix();

// 应用旋转并绘制花托
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(), vLightEyePos, vTorusColor);
torusBatch.Draw();
modelViewMatrix.PopMatrix(); // "清除" 以前的旋转

//绘制三角形
//modelViewMatrix.PushMatrix();
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(), vLightEyePos, vTorusColor);
triangleBatch.Draw();
//modelViewMatrix.PopMatrix();

// 应用另一个旋转,然后进行平移,然后再绘制球体
modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(), vLightEyePos, vSphereColor);
sphereBatch.Draw();

// 还原到以前的模型视图矩阵 (单位矩阵)
modelViewMatrix.PopMatrix();
modelViewMatrix.PopMatrix();
// 进行缓冲区交换
glutSwapBuffers();

// 通知GLUT在进行一次同样操作
glutPostRedisplay();
}

// Respond to arrow keys by moving the camera frame of reference
void SpecialKeys(int key, int x, int y)
{
float linear = 0.1f;
float angular = float(m3dDegToRad(5.0f));

if (key == GLUT_KEY_UP)
cameraFrame.MoveForward(linear);

if (key == GLUT_KEY_DOWN)
cameraFrame.MoveForward(-linear);

if (key == GLUT_KEY_LEFT)
cameraFrame.RotateWorld(angular, 0.0f, 1.0f, 0.0f);

if (key == GLUT_KEY_RIGHT)
cameraFrame.RotateWorld(-angular, 0.0f, 1.0f, 0.0f);
}

int main(int argc, char* argv[])
{
gltSetWorkingDirectory(argv[0]);

glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(800, 600);

glutCreateWindow("OpenGL SphereWorld");

glutSpecialFunc(SpecialKeys);
glutReshapeFunc(ChangeSize);
glutDisplayFunc(RenderScene);

GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}

SetupRC();
glutMainLoop();
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276

运行结果 

按方向键可以移动和旋转画面,可以看到三角形,小球围绕花圈转动,花圈自己在自旋转。 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: