变换
进行图形开发,3D图形数学基础是必不可少,限于其专业性,OpenGL笔记里不做详细赘述。但是对相关术语及其在OpenGL对应的内容还是需要一定的了解。
基础
向量
点乘
两个(三分量)单位向量之间的点乘运算将得到一个标量(只有一个值),它表示两个向量之间的夹角。要进行这种运算,这两个向量必须为单位长度,而返回的结果将在-1.0到1.0之间。这个数字实际上就是这两个向量之间夹角的余弦值。
我们可以使用m3dDotProduct3函数来实际获得两个向量之间的点乘结果。
float m3dDotProduct3(const M3DVector3f u,const M3DVector3f v);
m3dGetAngleBetweenVectors3返回这个角的弧度制。
float m3dGetAngleBetweenVectors3 (const M3DVector3f u,const M3DVector3f v);
叉乘
要进行叉乘,两个向量都不必为单位向量。
两个向量之间叉乘所得的结果是另外一个向量,这个新向量与原来两个向量定义的平面垂直。
m3dCrossProduct3对两个向量进行叉乘并返回运算得到的结果向量。
void m3dCrossproduct3(M3DVector3f result,const M3DVector3f u,const M3DVector3f v);
矩阵
矩阵是一个二维数组,一个矩阵只有一行或者一列也是合法的。
在我们进行3D程序设计工作时,我们将使用的几乎全部是3X3和4X4矩阵。math3d库中有这两种矩阵数据类型:
typedef float M3DMatrix33f[9];
typedef float M3DMatrix44f[16];
变换
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);
旋转
m3dRotationMatrix44(M3DMatrix44f m,float angle,float x,float y,float z);
这个函数让我们围绕一个由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);
综合变换
math3d库函数m3dMatrixMultiply44用来将两个矩阵相乘并返回运算结果。
void m3dMatrixMultiply44(M3DMatrix44f product,const M3DMatrix44f a,const M3DMatrix44f b);
运用模型视图矩阵
void RenderScene(void)
{
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);
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();
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);
透视投影
GLFrustum::SetPerspective(float fFov,float fAspect,float fNear,float fFar);
变换管线
使用矩阵堆栈
GLMatrixStack类的构造函数允许指定堆栈的最大深度,默认的堆栈深度为64.这个矩阵堆栈在初始化时已经在堆栈中包含了单位矩阵。
GLMatrixStack::GLMatrixStack(int iStackDepth=64);
我们可以通过调用在顶部载入这个单位矩阵。
void GLMatrixStack::LoadIdentity(void);
或者可以在堆栈顶部载入任何矩阵。
void GLMatrixStack::LoadMatrix(const M3DMatrix44f m);
此外,我们可以用一个矩阵乘以矩阵堆栈的顶部矩阵,相乘得到的结果随后将存储在堆栈的顶部。
void GLMatrixStack::MultMatrix(const M3DMatrix44f);
最后,只要用GetMatrix函数就可以获得矩阵堆栈顶部的值,这个函数可以进行两次重载,以适应GLShaderManager的使用,或者仅仅是获得顶部矩阵的副本。
const M3DMatrix44f& GLMatrixStack::GetMatrix(void);
void GLMatrixStack::GetMatrix(M3DMatrix44f mMatrix);
压栈与出栈
void GLMatrixStack::PushMatrix(void);
void PushMatrix(const M3DMatrix44f mMatrix);
void PushMatrix(GLFrame& frame);
void GLMatrixStack::PopMatrix(void);
仿射变换
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);
管理管线
示例:
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);
}
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();
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);
光线
将一个固定光源位置变换到视觉坐标在每个成精中只需进行一次:
M3DVector4f vLightPos={0.0f,10.0f,5.0f,1.0f};
M3DVector4f VLightEyePos;
m3dTransformVector4(vLightEyePos,vLightPos,mCamera);
例如要渲染一个蓝色球体:
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(),transformPipeline,GetProjectionMatrix(),vLightEyePos,vSphereColor)
更多对象
三角形批次类
首先,我们需要为对象创建一个事件。
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>
#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;
GLMatrixStack modelViewMatrix;
GLMatrixStack projectionMatrix;
GLFrustum viewFrustum;
GLGeometryTransform transformPipeline;
GLTriangleBatch torusBatch;
GLBatch floorBatch;
GLTriangleBatch sphereBatch;
GLTriangleBatch triangleBatch;
GLTriangleBatch cylinderBatch;
GLTriangleBatch diskBatch;
GLFrame cameraFrame;
void SetupRC()
{
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();
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();
}
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);
}
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.Rotate(yRot, 0.0f, 1.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(), vLightEyePos, vTorusColor);
triangleBatch.Draw();
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();
glutPostRedisplay();
}
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
运行结果
按方向键可以移动和旋转画面,可以看到三角形,小球围绕花圈转动,花圈自己在自旋转。