2.3 渲染管线

一旦我们描述几何学上的3D场景和设置了虚拟照相机,我们要把这个场景转换成2D图象显示在显示器上。这一系列必须完成的操作就叫做渲染管线。图2.7展示了一个简化的渲染管线,随后将详细解释图中的每一部分。

渲染管线中的许多级都是从一个坐标系到另一个坐标的几何变换。这些变换都通过矩阵变换来实现。Direct3D为我们进行变换计算并且如果显卡支持硬件变换的话那就更有利了。使用Direct3D进行矩阵变换,我们唯一要做的事就是提供从一个系统变换到另一个系统的变换矩阵就可以了。我们使用IDirect3DDevice9::SetTranform方法提供变换矩阵。它输入一个表示变换类型的参数和一个变换矩阵。如图2.7所示,为了进行一个从自身坐标系到世界坐标系的变换,我们可以这样写:

Device->SetTransform(D3DTS_WORLD, &worldMatrix);

2.3.1自身坐标系(Local Space)

自身坐标系又叫做建模空间,这是我们定义物体的三角形列的坐标系。自身坐标系简化了建模的过程。在物体自己的坐标系中建模比在世界坐标系中直接建模更容易。例如,在自身坐标系中建模不像在世界坐标系中要考虑本物体相对于其他物体的位置、大小、方向关系。图 2.8所示是一个在自身局部坐标系中定义的茶壶。

2.3.2世界坐标系(World Space)

一旦我们构造了各种模型,它们都在自己的自身坐标系中,但是我们需要把它们都放到同一个世界坐标系中。物体从自身坐标系到世界坐标系中的变换叫做世界变换。世界变换通常是用平移、旋转、缩放操作来设置模型在世界坐标系中的位置、大小、方向。世界变换就是通过各物体在世界坐标系中的位置、大小和方向等相互之间的关系来建立所有物体。图2.9所示是相对于世界坐标系描述的几个3D物体。

世界变换由一个矩阵表示,并且在Direct3D中调用IDirect3DDevice9::SetTransform方法设置它,记住将转换类型设为D3DTS_WORLD。例如我们要在世界坐标系中放置一个立方体定位在(-3,2,6)和一个球体定位在(5,0,-2),我们可以这样写程序:

//创建立方体的世界矩阵(一个平移矩阵)

D3DXMATRIX cubeWorldMatrix;

D3DXMatrixTranslation(&cubeWorldMatrix, -3.0f, 2.0f, 6.0f);

//创建球体的世界矩阵(一个平移矩阵)

D3DXMATRIX sphereWorldMatrix;

D3DXMatrixTranslation(&sphereWorldMatrix, 5.0f, 0.0f, -2.0f);

// 变换立方体,然后绘制它

Device->SetTransform(D3DTS_WORLD, &cubeWorldMatrix);

drawCube(); // draw the cube

// 因为球体使用一个不同的世界变换,我们必须更改世界矩阵为球体的,

// 如果不更改,球体将绘制在上一个世界矩阵的位置上(立方体的世界矩阵)

Device->SetTransform(D3DTS_WORLD, &sphereWorldMatrix);

drawSphere(); // 绘制球体

这是个非常简单的实例,没有用到矩阵的旋转和缩放。但是一般很多物体都需要进行这些变换,不过这个例子也还是展示了世界变换是怎样进行的。

2.3.3视图坐标系(View Space)

世界坐标系中的几何图与照相机是相对于世界坐标系而定义的,如图2.10所示。然而在世界坐标系中当照相机是任意放置和定向时,投影和其它一些操作会变得困难或低效。为了使事情变得更简单,我们将照相机平移变换到世界坐标系的原点并把它的方向旋转至朝向Z轴的正方向,当然,世界坐标系中的所有物体都将随着照相机的变换而做相同的变换。这个变换就叫做视图坐标系变换(view space transformation)。

视图坐标的变换矩阵可以通过如下的D3DX函数计算得到:

D3DXMATRIX *D3DXMatrixLookAtLH(

       D3DXMATRIX* pOut, // 指向返回的视图矩阵

       CONST D3DXVECTOR3* pEye, // 照相机在世界坐标系的位置

       CONST D3DXVECTOR3* pAt, // 照相机在世界坐标系的目标点

       CONST D3DXVECTOR3* pUp // 世界坐标系的上方向(0, 1, 0)

);

pEye参数指定照相机在世界坐标系中的位置,pAt参数指定照相机所观察的世界坐标系中的一个目标点,pUp参数指定3D世界中的上方向,通常设Y轴正方向为上方向,即取值为(0,1,0)。

例如:假设我们要把照相机放在点(5,3,-10),并且目标点为世界坐标系的中点(0,0,0),我们可以这样获得视图坐标系变换矩阵:

D3DXVECTOR3 position(5.0f, 3.0f, –10.0f);

D3DXVECTOR3 targetPoint(0.0f, 0.0f, 0.0f);

D3DXVECTOR3 worldUp(0.0f, 1.0f, 0.0f);

D3DXMATRIX V;

D3DXMatrixLookAtLH(&V, &position, &targetPoint, &worldUp);

视图坐标系变换也是通过IDirect3DDevice9::SetTransform来实现的,只是要将变换类型设为D3DTS_VIEW,如下所示:

Device->SetTransform(D3DTS_VIEW, &V);

2.3.4背面消除(Backface Culling)

一个多边形有两个表面,我们将一个标为正面,一个为背面。通常,后表面总是不可见的,这是因为场景中大多数物体是密封的。例如盒子、圆柱体、箱子、角色等,并且我们也不能把照相机放入物体的内部。因此照相机永不可能看到多边形的背面。这是很重要的,如果我们能看背面,那么背面拣选就不可能工作。

图2.11表示了一个物体在视图坐标系中的正面。一个多边形的边都是面向照相机叫正面多边形,而一个多边形的边都背对照相机叫背面多边形。

由图2.11可知,正面多边形挡住了在它后面的背面多边形,Direct3D将通过消除(即删除多余的处理过程)背面多边形来提高效率,这种方法就叫背面拣选。图2.12展示了背面拣选之后的多边形,从照相机的观察点来看,仍将绘制相同的场景到后备表面,那些被遮住的部分无论如何都永远不会被看见的。

当然,为了完成这项工作,Direct3D需要知道哪个多边形是正面,哪个是背面。Direct3D中默认顶点以顺时针方向(在观察坐标系中)形成的三角形为正面,以逆时针方向形成的三角形为背面。

如果我们不想使用默认绘制状态,我们可以通过改变D3DRS_CULLMODE来改变渲染状态:

Device->SetRenderState(D3DRS_CULLMODE, Value);

Value可以是如下一个值:

D3DCULL_NONE——完全不使用背面消除

D3DCULL_CW——消除顺时针方向环绕的三角形

D3DCULL_CCW——消除逆时针方向环绕的三角形,这是默认值。

2.3.5光照(Lighting)
光照定义在世界坐标系中,但必须变换到视图坐标系才可使用。视图坐标系中光源给物体施加的光照大大增加了场景中物体的真实性。
2.3.6裁剪(Clipping)

我们删除那些超出了可视体范围的几何图形的过程就叫做裁剪。这会出现三种情况:

完全包含——三角形完全在可视体内,这会保持不变,并进入下一级。

完全在外——三角形完全在可视体外部,这将被删除。

部分在内(部分在外)——三角形一部分在可视体内,一部分在可视体外,则三角形将被分成两部分,可视体内的部分被保留,可视体之外的则被删除。

图2.13展示了上面三种情况:

2.3.7投影(Projection)

视图坐标系的主要任务就是将3D场景转化为2D图像表示。这种从n维转换成n-1维的过程就叫做投影。投影的方法有很多种,但是我们只对一种特殊的投影感兴趣,那就是透视投影。因为透视投影可以使离照相机越远的物体投影到屏幕上后就越小,这可以使我们把3D场景更真实的转化为2D图像。图2.14展示了一个3D空间中的点是如何通过透视投影到投影窗口上去的。

投影变换的实质就是定义可视体并将可视体内的几何图形投影到投影窗口上去。投影矩阵的计算太复杂了,这里我们不会给出推导过程,而是使用如下的Direct3D函数通过给出平截头体的参数来求出投影矩阵。

D3DXMATRIX *D3DXMatrixPerspectiveFovLH(

       D3DXMATRIX* pOut, // 返回的投影矩阵

       FLOAT fovY, // 用弧度表示的视野角度vertical field of view angle in radians

       FLOAT Aspect, // 宽高比

       FLOAT zn, // 前裁剪面距离

       FLOAT zf // 后裁剪面距离

);

(fovY定义镜头垂直观察范围,以弧度为单位。对于这个参数,下面是我的理解:如果定义为D3DX_PI/2(90度角),那么就是表示以摄像机的观察方向为平分线,上方45度角和下方45度角就是摄像机所能看到的垂直范围了。嗯,可以想象一下自己的眼睛,如果可以把自己眼睛的fovY值设为D3DX_PI(180度角),那么我们就可以不用抬头就看得见头顶的东西了。如果设为2 x D3DX_PI的话。。。我先编译一下试试(building…)。哈哈,结果啥也看不见。很难想象如果自己能同时看到所有方向的物体,那么将是一个怎样的画面啊)

如图2.15所示视锥的描述参数。

Aspect参数为投影平面的宽高比例值,由于最后都为转换到屏幕上,所以这个比例一般设为屏幕分辨率的宽和高的比值。如果投影窗口是个正方形,而我们的显示屏一般都是长方形的,这样转换后就会引起拉伸变形。

aspectRation = screenWidth / screenHeight

我们还是通过调用IDirect3DDevice9::SetTranform方法来进行投影变换,当然,要把第一个投影类型的参数设为D3DTS_PROJECTION。下面的例子基于一个90度视角、前裁剪面距离为1、后裁剪面距离为1000的平截头体创建投影矩阵:

D3DXMATRIX proj;

D3DXMatrixPerspectiveFovLH(&proj, PI * 0.5f, (float)width / (float)height, 1.0, 1000.0f);

Device->SetTransform(D3DTS_PROJECTION, &proj);

2.3.8视口变换(Viewport Transform)

视口变换主要是转换投影窗口到显示屏幕上。通常一个游戏的视口就是整个显示屏,但是当我们以窗口模式运行的时候,也有可能只占屏幕的一部分或在客户区内。视口矩形是由它所在窗口的坐标系来描述的,如图2.16。

在Direct3D中,视口矩形通过D3DVIEWPORT9结构来表示。它的定义如下:

typedef struct _D3DVIEWPORT9 {

       DWORD X;

       DWORD Y;

       DWORD Width;

       DWORD Height;

       DWORD MinZ;

       DWORD MaxZ;

} D3DVIEWPORT9;

前四个参数定义了视口矩形与其所在窗口的关系。MinZ成员指定最小深度缓冲值,MaxZ指定最大深度缓冲值。Direct3D使用的深度缓冲的范围是0~1,所以如果不想做什么特殊效果的话,将它们分别设成相应的值就可以了。

一旦我们填充完D3DVIEWPORT9结构后,就可以如下设视口:

D3DVIEWPORT9 vp{ 0, 0, 640, 480, 0, 1 };

Device->SetViewport(&vp);

这样,Direct3D就会自动为我们处理视口变换。现在还是给出视口变换矩阵作为参考:

2.3.9光栅化(Rasterization)

在把三角形每个顶点转换到屏幕上以后,我们就画了一个2D三角形。光栅化是计算需要显示的每个三角形中每个点颜色值(如图2.17)。

光栅化过程是非常繁重的计算,它应该通过硬件图形处理来完成。它的处理结果就是把2D图象显示在显示器上。

本文主要介绍并行端口的结构以及简单的对并口的读、写并以及如何获得端口的状态。   

并行接口的分类: SPP(标准并行接口) ,EPP(增强型并行接口),ECP(扩展型并行端口)

  标准并行端口(SPP)也是最早的端口定义,主要功能如下,1:并行端口提供了8个数据线以进行并行的字节传输,2:计算机能够通过数据线向打印机发送选能信号,以通知打印机已经准备好接收数据,3:打印机招收到数据后,向计算机发送一个回应信号(NACK)。其各位信号线所代表的意义详见下表。

  增强型并行端口(EPP)的出现提供了一种更高性能的连接方式,并东路向下兼容所有在此之前存在的并行接口及外设。与SPP不同之处在于原来17个信号中的重新定义,在这17个信号中,EPP使用了其中的14个信号进行传输,握手和选通,剩下的3个信号可以由外设设计者有来自定义。

  并行接口的大致结构:

      并行口一般有25个引脚,其中包括8位数据线,5位打印机状态线,4位控制线.下面将对这些引脚予以详细说明:

  (注:1:>出,表示由计算机发向打印机;入,表示由打印机发向计算机,

      2:>低电平有效信号用上划线或星号表示(如S7*),高电平有效信号则没有上划线或星号)

      

<tbody>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><strong><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">引脚号</span><span lang="EN-US"><O:P> </O:P></span></strong></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><strong><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">名称</span><span lang="EN-US"><O:P> </O:P></span></strong></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt" width="101"><strong><span style="font-family: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">数据位</span></strong> </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt" width="101"><strong><span style="font-family: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">寄存器</span></strong> </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><strong><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">数据方向</span><span lang="EN-US"><O:P> </O:P></span></strong></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt" width="233">

        <p class="MsoNormal" style="text-align: center" align="center"><strong><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">定义</span><span lang="EN-US"><O:P> </O:P></span></strong></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">1</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">/STROBE</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101"><span style="font-family: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">C0*</span> </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">  </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">出</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="233">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">选通信号,低电平有效信号,表明线上有数据到达.</span></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">2</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">D0</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101" rowspan="8"> DATA_1-DATA_8

        <p> </p>

         

        <p> </p>

         

        <p> </p>

         

        <p>&nbsp;</p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101" rowspan="8">D1-D8 </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">出</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="233" rowspan="7">

        <p class="MsoNormal" style="text-align: center" align="center">&nbsp;<span lang="EN-US"><O:P> </O:P></span></p>

        <p class="MsoNormal" style="text-align: center" align="center">&nbsp;<span lang="EN-US"><O:P> </O:P></span></p>

        <p class="MsoNormal" style="text-align: center" align="center">&nbsp;<span lang="EN-US"><O:P> 八位数据线,只有在SPP指令下才有能输出数据.</O:P> </span></p>

        <p class="MsoNormal" style="text-align: center" align="center">&nbsp;<span lang="EN-US"><O:P> </O:P></span></p>

        <p class="MsoNormal" style="text-align: center" align="center">&nbsp;<span lang="EN-US"><O:P> </O:P></span></p>

        <p class="MsoNormal" style="text-align: center" align="center">&nbsp;<span lang="EN-US"><O:P> </O:P></span></p>

        <p class="MsoNormal" style="text-align: center" align="center">&nbsp;<span lang="EN-US"><O:P> </O:P></span></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">3</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">D1</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">出</span><strong><span lang="EN-US"><O:P> </O:P></span></strong></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">4</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">D2</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">出</span></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">5</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">D3</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">出</span></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">6</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">D4</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">出</span></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">7</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">D5</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">出</span></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">8</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">D6</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">出</span></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">9</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">D7</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">出</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="233">

        <p class="MsoNormal" style="text-align: center" align="center">&nbsp;<span lang="EN-US"><O:P> </O:P></span></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">10</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">/ACK</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101"> S6 </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">Status </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">入</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="233">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">应答,以插入低电平的形式出现,表明最后一个字符已招收完毕。</span></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">11</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">BUSY</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101"> 

        <p>S7*</p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">Status </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">入</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="233">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">繁忙通知,以插入高电平的方式出现,表明打印机处于忙状态不能再接收数据。</span></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">12</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">PE</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">S5 </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">Status </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">入</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="233">

        <p class="MsoNormal" align="center"><span style="font-family: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">没有打印机纸。</span> </p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">13</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">SELECT</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101"> S4 </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">Status </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">入</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="233">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">选择输入,以插入高电平的方式出现,表明打印机处于在线待命状态。</span></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">14</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">AUTO FEED</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101"> C1* </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">Control </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">出</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="233">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">自动馈送,低电平有效信号民,通知打印机对于每遇到一个回车进行自动换行。</span></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">15</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">/ERROR</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">S3 </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">Status </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">入</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="233">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">错误,该信号由打印机发向计算机,表明打印机处于错误状态。</span></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">16</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">/INIT</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101"> C2 </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">Control </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">出</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="233">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">初始化,低电平有效信号,该信号用来对打印机进行复位。</span></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">17</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">/SELIN</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101"> C3* </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">Control </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">出</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="233">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">选择输入,低电平有效信号,表明已经选中的打印机。</span></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">18</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">GND</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">  </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">  </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">---</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="233">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">信号接地</span></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">19</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">GND</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">  </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">  </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">---</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="233">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">信号接地</span></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">20</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">GND</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">  </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">  </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">---</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="233">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">信号接地</span></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">21</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">GND</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">  </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">  </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">---</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="233">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">信号接地</span></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">22</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">GND</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">  </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">  </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">---</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="233">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">信号接地</span></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">23</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">GND</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">  </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">  </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">---</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="233">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">信号接地</span></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">24</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">GND</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">  </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">  </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">---</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="233">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">信号接地</span></p>

        </td>

    </tr>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">25</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">GND</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">  </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">  </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="101">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">---</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: medium none; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" width="233">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">信号接地</span></p>

        </td>

    </tr>

</tbody>

    打印机常用端口: 对于LPT1:0X378 为数据发送地址,0X379为打印机状态地址,0X37A为计算机向打印机控制地址, 通常为了使程序具有通用性我们可以从注册表中取得这个地址,对于WINDOWS CE 而言,这个值被存储在{HKEY_LOCAL_MACHINE//DRIVERS//BUILTIN//PARALLEL//IOBASE}.然后我们就可以通过对此三个端口进行控制达到简单的并口编程的目的.

下面是对打印机状态端口及打印机控制端口作一详细解释:

控制端口:

<tbody>

    <tr>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; width: 33.1pt; padding-top: 0cm; border-bottom: windowtext 0.5pt solid" width="55">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">0x37A</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; width: 33.1pt; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt" width="55">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">1</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; width: 33.15pt; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt" width="55">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">1</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; width: 33.1pt; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt" width="55">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">输入控制</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; width: 33.15pt; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt" width="55">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">中断</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; width: 33.1pt; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt" width="55">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">17</span><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">线</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; width: 33.15pt; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt" width="55">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">16 </span><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">线</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; width: 33.1pt; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt" width="55">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">14 </span><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">线</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; width: 33.15pt; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; mso-border-left-alt: solid windowtext .5pt" width="55">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">1<span style="mso-spacerun: yes">&nbsp; </span></span><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">线</span></p>

        </td>

    </tr>

</tbody>

  这个0x37A计算机控制打印机的地址,可以产生对打印机进行控制的必要信号,可写,两高位(7和8)没什么用,第6位写1表示可以向并口输出数据。第五位中断信号(IRQ EN),利用此信号线,驱动程序可以在STATUS端口信号(nAck)的帮助下,使用该信号对中断信号的产生与否进行控制。第3210位分别控制第17线,第16线,第14线和第1线。(可以控制它们的状态)具体作用可参见前表

状态端口:

<tbody>

    <tr style="height: 18.05pt">

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 0.5pt solid; width: 32.55pt; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; height: 18.05pt" width="54">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">0x379</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; width: 33.75pt; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; height: 18.05pt; mso-border-left-alt: solid windowtext .5pt" width="56">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">(S7)11 </span><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">忙</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; width: 33.75pt; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; height: 18.05pt; mso-border-left-alt: solid windowtext .5pt" width="56">

        <p class="MsoNormal" style="text-align: center" align="center"><span style="font-family: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">(S6)10应答</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; width: 33.75pt; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; height: 18.05pt; mso-border-left-alt: solid windowtext .5pt" width="56">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">(S5)12</span>缺纸</p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; width: 33.75pt; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; height: 18.05pt; mso-border-left-alt: solid windowtext .5pt" width="56">

        <p class="MsoNormal" style="text-align: center" align="center">(S4)13联机</p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; width: 33.75pt; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; height: 18.05pt; mso-border-left-alt: solid windowtext .5pt" width="56">

        <p class="MsoNormal" style="text-align: center" align="center"><span lang="EN-US">(S3)15 </span><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">错误</span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; width: 33.75pt; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; height: 18.05pt; mso-border-left-alt: solid windowtext .5pt" width="56">

        <p class="MsoNormal" style="text-align: center" align="center">&nbsp;<span lang="EN-US"><O:P> S2</O:P> </span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; width: 33.75pt; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; height: 18.05pt; mso-border-left-alt: solid windowtext .5pt" width="56">

        <p class="MsoNormal" style="text-align: center" align="center">S1&nbsp;<span lang="EN-US"><O:P> </O:P></span></p>

        </td>

        <td style="border-right: windowtext 0.5pt solid; padding-right: 5.4pt; border-top: windowtext 0.5pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: medium none; width: 33.75pt; padding-top: 0cm; border-bottom: windowtext 0.5pt solid; height: 18.05pt; mso-border-left-alt: solid windowtext .5pt" width="56">

        <p class="MsoNormal" style="text-align: center" align="center">S0&nbsp;<span lang="EN-US"><O:P> </O:P></span></p>

        </td>

    </tr>

</tbody>

  0x379为打印机状态地址,可读,通过个端口打印机适配器可以很方便的读取打印机的状态。

标记为S7的信号表示最高位,SO表示为最低位,只有S3-S7五个信号才是真正有用的信号。他们的具体信号功能解释如下:

S7*(busy):打印机使用该信号表示打印机正处于忙状态,不能再接收数据。需要强调的是,该信号通过适配器板时,进行了反相处理,因此连接器上的低电平到微处理器时就变成了高电平。

S6(nAck):当适配器发出选通信号时,打印机就会产生该信号作为响应。通常情况下,该信号是高电平,选通打印机之后,打印机首先把该信号设为低电平,然后再返回高电平。

S5(PE):当打印机缺纸时,它就会产生一个这样的信号,通常情况下,该信号由打印机保持为低电平,打印机纸使用完之后,该信号就会变成高电平。

S4(SELECT):当打印机恢复正常操作时,它就会插入一个高电平的该信号。当打印机处于无效状态时,访信号就会变成低电平。

S3(NERROR):当打印机出现错误时就会产生这种邮错信号。产生出错的原因很多,如打印纸堵了或产生了内部错误。产生错误时该信号就会设置成低电平。

以下为在Windows CE 下打印机各种状态时,所对应的状态寄存器的(AL)的值:

1:>在没接入打印机时寄存器AL值为127,对应二进制是:1111111

2:>打印机在缺纸灯不亮时寄存器AL值为144,对应二进制是:10010000

3:>打印机在缺纸灯亮时寄存器AL值为 119;对应二进制值是:11101111

4:>打印机在不缺纸的情况下寄存器AL值 223,对应二进制值是:11011111

5:>打印机在没开机的情况下得到AH值为207,对应二进制值是:11001111 

下面介绍对并口的编程控制:

(编程控制示例)(为汇编代码)

// 此段代码为并口向打印机进行写数据,并发送控制信息。

#define LPT_CLEAR_MASK 0x40

#define LPT_STROBE_HI 0x0D

#define LPT_STROBE_LO 0x0C


#define LPT_STATUS_BITS 0xF8

#define LPT_BITS_INVERT 0x48

#define LPT_NOTBUSY 0x80

#define LPT_PAPEROUT 0x20

#define LPT_SELECT 0x10

#define LPT_NFAULT 0x08


#define LPT_TimeOut 0x01


void OutByte(ULONG dataport, BYTE databyte) {

#if x86

_asm {

mov dx, word ptr [dataport]

mov al, byte ptr [databyte]

out dx, al

out dx, al

add dx, 2

in al, dx

and al, LPT_CLEAR_MASK

mov cl, al

or al, LPT_STROBE_HI

out dx, al

out dx, al

out dx, al

out dx, al

or cl, LPT_STROBE_LO

mov al, cl

out dx, al

}

#else



WRITE_PORT_UCHAR((PUCHAR)dataport,databyte);


dataport+=2;

databyte = (READ_PORT_UCHAR((PUCHAR)dataport) & LPT_CLEAR_MASK)

| LPT_STROBE_HI;

WRITE_PORT_UCHAR((PUCHAR)dataport,databyte);


databyte = (READ_PORT_UCHAR((PUCHAR)dataport) & LPT_CLEAR_MASK)

| LPT_STROBE_LO;

WRITE_PORT_UCHAR((PUCHAR)dataport,databyte);


#endif

}

// 此段代码为读取打印机当前状态。

BYTE CheckStatus(unsigned port) {

BYTE bRet;


#if x86

_asm {

mov dx, word ptr[port]

Checkme:

in al, dx

mov ah, al

nop

nop

in al, dx

cmp al, ah

jnz Checkme

and ah, LPT_STATUS_BITS

xor ah, LPT_BITS_INVERT

test ah, LPT_PAPEROUT or LPT_NFAULT

jnz CS_HasError

test ah, LPT_SELECT

jz CS_HasError

and ah, LPT_NOTBUSY

jz CS_HasError

xor eax, eax

CS_HasError:

mov [bRet],al

}

#else


BYTE bStatus;

do {

bRet= bStatus= READ_PORT_UCHAR((PUCHAR)port);

} while (bStatus != READ_PORT_UCHAR((PUCHAR)port));

bStatus&= LPT_STATUS_BITS;

bStatus^= LPT_BITS_INVERT;


if (!(bStatus & (LPT_PAPEROUT | LPT_NFAULT)) &&

(bStatus & LPT_SELECT) && (bStatus & LPT_NOTBUSY))

bRet=0;

#endif

return bRet;

}

// The End

@echo off

setlocal enabledelayedexpansion

set b=/-\ /-\ **

set 速度=1

set 退格=

:b

for /l %%i in (0,1,200) do call :a %%i

goto :b

:a

set/a a=%1%%10

set/a c=%a%%%4

if %a% EQU 0 set/p=▌<nul

if %c% EQU 3 (set/p=^|<nul) else (set/p=!b:~%a%,1!<nul)

ping/n %速度% 127.1>nul

set/p=%退格%<nul

goto :eof

//以下代码来自网上,需DDK、SDK支持





#include <windows.h>

#include <Setupapi.h>

#include <winioctl.h>

#include <cfgmgr32.h>

#include <regstr.h>  

#include <initguid.h>  

#include <stdio.h>
extern   "C"   
{    
 #include   "hidsdi.h"    
}  
DEFINE_GUID(GUID_DEVINTERFACE_DISK,                   0x53f56307L, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);
DEFINE_GUID(GUID_DEVINTERFACE_CDROM,                  0x53f56308L, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);
//-------------------------------------------------
DEVINST GetDrivesDevInstByDiskNumber(long DiskNumber, UINT DriveType);
//-------------------------------------------------
//-------------------------------------------------
int main(int argc, char* argv[])
{
 if ( argc != 2 ) {
  return 1;
 }
 
 //printf("in \n\n");
 
 char DriveLetter = argv[1][0];
 DriveLetter &= ~0x20; // uppercase
 
 if ( DriveLetter < 'A' || DriveLetter > 'Z' )
 {
  return 1;
 }
 
 char szRootPath[] = "X:\\";   // "X:\"
 szRootPath[0] = DriveLetter;
 
 char szVolumeAccessPath[] = "
\\\\.\\X:";   // "\\.\X:"
 szVolumeAccessPath[4] = DriveLetter;
 
 long DiskNumber = -1;
 
 HANDLE hVolume = CreateFile(szVolumeAccessPath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL);
 if (hVolume == INVALID_HANDLE_VALUE) {
  return 1;
 }
 
 STORAGE_DEVICE_NUMBER sdn;
 DWORD dwBytesReturned = 0;
 long res = DeviceIoControl(hVolume, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &dwBytesReturned, NULL);
 if ( res ) {
  DiskNumber = sdn.DeviceNumber;
 }
 CloseHandle(hVolume);
 
 if ( DiskNumber == -1 ) {
  return 1;
 }
 
 UINT DriveType = GetDriveType(szRootPath);
 
 DEVINST DevInst = GetDrivesDevInstByDiskNumber(DiskNumber, DriveType);
 
 if ( DevInst == 0 ) {
  return 1;
 }
 
 ULONG Status = 0;
 ULONG ProblemNumber = 0;
 PNP_VETO_TYPE VetoType = PNP_VetoTypeUnknown;
 WCHAR VetoNameW[MAX_PATH];
 bool bSuccess = false;
 
 res = CM_Get_Parent(&DevInst, DevInst, 0);  // disk's parent, e.g. the USB bridge, the SATA port....
 res = CM_Get_DevNode_Status(&Status, &ProblemNumber, DevInst, 0);
 bool IsRemovable = ((Status & DN_REMOVABLE) != 0);
 
 
 for ( long tries=1; tries<=3; tries++ ) { // sometimes we need some tries...
  VetoNameW[0] = 0;
  if ( IsRemovable ) {
   res = CM_Request_Device_EjectW(DevInst, &VetoType, VetoNameW, sizeof(VetoNameW), 0);
   //res = CM_Request_Device_EjectW(DevInst, &VetoType, NULL, 0, 0);  // with MessageBox or 'bubble'
  } else {
   res = CM_Query_And_Remove_SubTreeW(DevInst, &VetoType, VetoNameW, sizeof(VetoNameW), 0); // CM_Query_And_Remove_SubTreeA is not implemented under W2K!
  }
  bSuccess = (res==CR_SUCCESS && VetoType==PNP_VetoTypeUnknown);
  if ( bSuccess )  {
   break;
  } else {
   Sleep(200); // required to give the next tries a chance!
  }
 }
 
 if ( bSuccess ) {
  printf("Success\n\n");
  return 0;
 }
 
 printf("failed\n");
 
 printf("Result=0x%2X\n", res);
 
 if ( VetoNameW[0] ) {
  printf("VetoName=%ws)\n\n", VetoNameW);
 }
 return 1;
}
//-----------------------------------------------------------
 

//-----------------------------------------------------------
DEVINST GetDrivesDevInstByDiskNumber(long DiskNumber, UINT DriveType) {
 
 GUID* guid;
 
 switch (DriveType) {
 case DRIVE_REMOVABLE:
  //break;
 case DRIVE_FIXED:
  guid = (GUID*)(void*)&GUID_DEVINTERFACE_DISK;
  break;
 case DRIVE_CDROM:
  guid = (GUID*)(void*)&GUID_DEVINTERFACE_CDROM;
  break;
 default:
  return 0;
 }
 
 // Get device interface info set handle for all devices attached to system
 HDEVINFO hDevInfo = SetupDiGetClassDevs(guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
 
 if (hDevInfo == INVALID_HANDLE_VALUE){
  return 0;
 }
 
 // Retrieve a context structure for a device interface of a device
 // information set.
 DWORD dwIndex = 0;
 SP_DEVICE_INTERFACE_DATA devInterfaceData = {0};
 devInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
 BOOL bRet = FALSE;
 
 BYTE Buf[1024];
 PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)Buf;
 SP_DEVICE_INTERFACE_DATA         spdid;
 SP_DEVINFO_DATA                  spdd;
 DWORD                            dwSize;
 
 spdid.cbSize = sizeof(spdid);
 
 while ( true ){
  bRet = SetupDiEnumDeviceInterfaces(hDevInfo, NULL, guid, dwIndex, &devInterfaceData);
  if (!bRet) {
   break;
  }
  
  SetupDiEnumInterfaceDevice(hDevInfo, NULL, guid, dwIndex, &spdid);
  
  dwSize = 0;
  SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, NULL, 0, &dwSize, NULL);
  
  if ( dwSize!=0 && dwSize<=sizeof(Buf) ) {
   //pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
   //if ( pspdidd == NULL ) {
   //return 0; // damn
   //}
   pspdidd->cbSize = sizeof(*pspdidd); // 5 Bytes!
   
   ZeroMemory((PVOID)&spdd, sizeof(spdd));
   spdd.cbSize = sizeof(spdd);
   
   long res = SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, pspdidd, dwSize, &dwSize, &spdd);
   if ( res ) {
    
    // the device instance id string contains the serial number if the
    // device has one...
    // char szDevInstId[260] = {0};
    // SetupDiGetDeviceInstanceId(hDevInfo, &spdd, szDevInstId, 260, NULL);
    // printf("DevInstId=%s\n", szDevInstId);
    
    HANDLE hDrive = CreateFile(pspdidd->DevicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL);
    if ( hDrive != INVALID_HANDLE_VALUE ) {
     STORAGE_DEVICE_NUMBER sdn;
     DWORD dwBytesReturned = 0;
     res = DeviceIoControl(hDrive, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &dwBytesReturned, NULL);
     if ( res ) {
      if ( DiskNumber == (long)sdn.DeviceNumber ) {
       CloseHandle(hDrive);
       //HeapFree(GetProcessHeap(), 0, pspdidd);
       SetupDiDestroyDeviceInfoList(hDevInfo);
       return spdd.DevInst;
      }
     }
     CloseHandle(hDrive);
    }
   }
   //HeapFree(GetProcessHeap(), 0, pspdidd);
  }
  dwIndex++;
 }
 
 SetupDiDestroyDeviceInfoList(hDevInfo);
 
 return 0;
}

首先得弄清楚同步、异步、阻塞、非阻塞的概念。

同步和异步是针对通讯的工作模式,阻塞和非阻塞是指socket的I/O操作。

实际上对于socket,只存在阻塞和非阻塞,同步与异步是在程序实现上有所不同。

以阻塞的方式执行recv函数,在没有收到数据前,此函数是不会返回的,所以这很容易执行函数的线程处于等待I/O上的数据状态,然后被挂起。非阻塞就不一样,执行recv时候不管有没有数据都立即返回,有数据时返回数据,没数据时返回错误。非阻塞可以带来程序的高效,也带来了写程序中必须注意的地方,非阻塞情况下,发送与接收数据时候,要用户自己管理自己的缓冲区,并且要记录发送与接受的位置,因为很可能发送与接受数据的任务不能一次完成,需要多次调用send和recv才可以完成。

本来同步异步是用来表示通讯模式的,通信的同步,主要是指客户端在发送请求后,必须得在服务端有回应后才发送下一个请求。所以这个时候的所有请求将会在服务端得到同步。通信的异步,指客户端在发送请求后,不必等待服务端的回应就可以发送下一个请求,这样对于所有的请求动作来说将会在服务端得到异步,这条请求的链路就象是一个请求队列,所有的动作在这里不会得到同步的。但是个人感觉,在说到socket的同步异步时候,同步跟阻塞概念差不多,都是有了结果才返回,异步则是告诉系统我要recv数据,然后马上返回,等待数据来了后,系统跟程序说数据到了,然后程序再recv数据。引用在网上看到的比较好的描述“阻塞 block 是指,你拨通某人的电话,但是此人不在,于是你拿着电话等他回来,其间不能再用电话。同步大概和阻塞差不多。非阻塞 nonblock 是指,你拨通某人的电话,但是此人不在,于是你挂断电话,待会儿再打。至于到时候他回来没有,只有打了电话才知道。即所谓的“轮询 / poll”。异步是指,你拨通某人的电话,但是此人不在,于是你叫接电话的人告诉那人(leave a message),回来后给你打电话(call back)。”

显然,异步要高效一些。在Winsock中实现异步的方法有很多,Winsock工作模型有下面六种

    一:select模型

    二:WSAAsyncSelect模型

    三:WSAEventSelect模型

    四:Overlapped I/O 事件通知模型

    五:Overlapped I/O 完成例程模型

    六:IOCP模型

从一到六越来越高级,越来越高效,实现越来越复杂。曾在网上看到一些比喻用来很好的说明这些模型,在这里引用一下。

    老陈有一个在外地工作的女儿,不能经常回来,老陈和她通过信件联系。他们的信会被邮递员投递到他们的信箱里。

一:select模型

老陈非常想看到女儿的信。以至于他每隔10分钟就下楼检查信箱,看是否有女儿的信~~~~~

在这种情况下,“下楼检查信箱”然后回到楼上耽误了老陈太多的时间,以至于老陈无法做其他工作。

二:WSAAsyncSelect模型

后来,老陈使用了微软公司的新式信箱。这种信箱非常先进,一旦信箱里有新的信件,盖茨就会给老陈打电话:喂,大爷,你有新的信件了!从此,老陈再也不必频繁上下楼检查信箱了,牙也不疼了,你瞅准了,蓝天......不是,微软~~~~~~~~

三:WSAEventSelect模型

后来,微软的信箱非常畅销,购买微软信箱的人以百万计数......以至于盖茨每天24小时给客户打电话,累得腰酸背痛,喝蚁力神都不好使~~~~~~

微软改进了他们的信箱:在客户的家中添加一个附加装置,这个装置会监视客户的信箱,每当新的信件来临,此装置会发出“新信件到达”声,提醒老陈去收信。盖茨终于可以睡觉了。

四:Overlapped I/O 事件通知模型

后来,微软通过调查发现,老陈不喜欢上下楼收发信件,因为上下楼其实很浪费时间。于是微软再次改进他们的信箱。新式的信箱采用了更为先进的技术,只要用户告诉微软自己的家在几楼几号,新式信箱会把信件直接传送到用户的家中,然后告诉用户,你的信件已经放到你的家中了!老陈很高兴,因为他不必再亲自收发信件了!

五:Overlapped I/O 完成例程模型

老陈接收到新的信件后,一般的程序是:打开信封----掏出信纸----阅读信件----回复信件......为了进一步减轻用户负担,微软又开发了一种新的技术:用户只要告诉微软对信件的操作步骤,微软信箱将按照这些步骤去处理信件,不再需要用户亲自拆信/阅读/回复了!老陈终于过上了小资生活!

六:IOCP模型

微软信箱似乎很完美,老陈也很满意。但是在一些大公司情况却完全不同!这些大公司有数以万计的信箱,每秒钟都有数以百计的信件需要处理,以至于微软信箱经常因超负荷运转而崩溃!需要重新启动!微软不得不使出杀手锏......

微软给每个大公司派了一名名叫“Completion Port”的超级机器人,让这个机器人去处理那些信件!


其实,上面每种模型都有优点,要根据程序需求而适当选择合适的模型,前面三种模型效率已经比较高,实现起来难道不大,很多一般的网络程序都采用前三种模型,只有对网络要求特别高的一些服务器才会考虑用后面的那些模型。MFC中的CAsyncSocket类就是用的WSAAsyncSelect模型,电驴中也是用的这种,不过在寻找对应socket的时候进行了优化,查找更快,在GridCast中采用的是WSAEventSelect模型,等待。


BTW:上面所说均在Windows平台下,只用WinSock才有这么多模型,在linux下,好像就只有第一种select模式,我对linux下的socket不是很了解,应该也有很多提高效率的地方。

在C++中,有三种类型的循环语句:for, while, 和do…while, 但是在一般应用中作循环时, 我们可能用for和while要多一些,do…while相对不受重视。

但是,最近在读我们项目的代码时,却发现了do…while的一些十分聪明的用法,不是用来做循环,而是用作其他来提高代码的健壮性。

1. do...while(0)消除goto语句。

通常,如果在一个函数中开始要分配一些资源,然后在中途执行过程中如果遇到错误则退出函数,当然,退出前先释放资源,我们的代码可能是这样:

version 1

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
bool Execute()
{
// 分配资源
int *p = new int;
bool bOk(true);

// 执行并进行错误处理
bOk = func1();
if(!bOk)
{
delete p;
p = NULL;
return false;
}

bOk = func2();
if(!bOk)
{
delete p;
p = NULL;
return false;
}


bOk = func3();
if(!bOk)
{
delete p;
p = NULL;
return false;
}

// ..........

// 执行成功,释放资源并返回
delete p;
p = NULL;
return true ;
}

这里一个最大的问题就是代码的冗余,而且我每增加一个操作,就需要做相应的错误处理,非常不灵活。于是我们想到了goto:

version 2

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
<div> bool   Execute() 
{
// 分配资源
int *p = new int;
bool bOk(true);

// 执行并进行错误处理
bOk = func1();
if (!bOk) goto errorhandle;

bOk = func2();
if (!bOk) goto errorhandle;

bOk = func3();
if (!bOk) goto errorhandle;

// ..........

// 执行成功,释放资源并返回
delete p;
p = NULL;

return true;

errorhandle:
delete p;
p = NULL;
return false;
}

代码冗余是消除了,但是我们引入了C++中身份比较微妙的goto语句,虽然正确的使用goto可以大大提高程序的灵活性与简洁性,但太灵活的东西往往是很危险的,它会让我们的程序捉摸不定,那么怎么才能避免使用goto语句,又能消除代码冗余呢,请看do…while(0)循环:

version3

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
bool Execute() 
{
// 分配资源
int *p = new int;
bool bOk(true);
do
{
// 执行并进行错误处理
bOk = func1();
if (!bOk) break;

bOk = func2();
if (!bOk) break;

bOk = func3();
if (!bOk) break;

// ..........
} while (0);

// 释放资源
delete p;
p = NULL;
return bOk;
}

“漂亮!”, 看代码就行了,啥都不用说了…

2 宏定义中的do...while(0)

如果你是C++程序员,我有理由相信你用过,或者接触过,至少听说过MFC, 在MFC的afx.h文件里面, 你会发现很多宏定义都是用了do…while(0)或do…while(false), 比如说:

1
#define AFXASSUME(cond) do { bool __afx_condVal=!!(cond); ASSERT(__afx_condVal); __analysis_assume(__afx_condVal); } while(0)

粗看我们就会觉得很奇怪,既然循环里面只执行了一次,我要这个看似多余的do…while(0)有什么意义呢?

当然有!

为了看起来更清晰,这里用一个简单点的宏来演示:

1
#define SAFE_DELETE(p) do{ delete p; p = NULL} while(0) 

假设这里去掉do…while(0),

1
#define SAFE_DELETE(p) delete p; p = NULL;  

那么以下代码:

1
2
if(NULL != p) SAFE_DELETE(p) 
else ...do sth...

就有两个问题,

  1. 因为if分支后有两个语句,else分支没有对应的if,编译失败
  2. 假设没有else, SAFE_DELETE中的第二个语句无论if测试是否通过,会永远执行。

你可能发现,为了避免这两个问题,我不一定要用这个令人费解的do…while, 我直接用{}括起来就可以了

1
#define SAFE_DELETE(p) { delete p; p = NULL;}  

的确,这样的话上面的问题是不存在了,但是我想对于C++程序员来讲,在每个语句后面加分号是一种约定俗成的习惯,这样的话,以下代码:

1
2
if(NULL != p) SAFE_DELETE(p); 
else ...do sth...

其else分支就无法通过编译了(原因同上),所以采用do…while(0)是做好的选择了。

也许你会说,我们代码的习惯是在每个判断后面加上{}, 就不会有这种问题了,也就不需要do...while了,如:

1
2
3
4
5
6
7
8
if(...)  
{

}
else
{

}

诚然,这是一个好的,应该提倡的编程习惯,但一般这样的宏都是作为library的一部分出现的,而对于一个library的作者,他所要做的就是让其库具有通用性,强壮性,因此他不能有任何对库的使用者的假设,如其编码规范,技术水平等。

软考室的烟味弥漫 坐满了程序员

室里面的监考官 系分 已三年

出上午试题的老师 练CPU 耍单片机

硬件功夫最擅长 还会逻辑门三极管

他们学生我习惯 从小就耳濡目染

什么软件跟网络我都耍的有摸有样

什么语言最喜欢 C++面向对象

想要去英伦美帝 学图灵诺伊曼

怎么编 怎么编 离散数学是关键

怎么编 怎么编 数值分析也较难

怎么编 怎么编 数据结构最重要

算法不学莫后悔 死的难看

一段代码写好 一个左子树 右子树

一句不会递归有危险 不停调用

一个优秀的库函 一用好多年 拷贝好带身边

怎么编 怎么编 我学会动态规划

怎么编 怎么编 分支限界的难关

怎么编 怎么编 已被我一脚踢开

哼 快使用C语言 哼哼哈兮

快使用C语言 哼哼哈兮

编程之人切记 NP无敌

是谁在练汇编 背指令集

快使用C语言 哼哼哈兮

快使用C语言 哼哼哈兮

如果我会分治 快速解题

熟用堆栈队列 系统分析

快使用C语言 哼

我用VB描述 哼

万能的回溯法

一般仿射变换

3x3矩阵仅能表达3D中的线性变换,不能包含平移。经过4x4矩阵的武装后,现在我们可以构造包含平移在内的一般仿射变换矩阵了。例如:

(1)绕不通过原点的轴旋转。

(2)沿不穿过原点的平面缩放。

(3)沿不穿过原点的平面镜像。

(4)向不穿过原点的平面正交投影。

它们的基本思想是将变换的"中心点"平移到原点,接着进行线性变换,然后再将"中心点"平移回原来的位置。开始使用平移矩阵T将点P移到原点,接着用线性变换矩阵R进行线性变换,最终的仿射变换矩阵M等于矩阵的积,即:TRT-1T-1是平移矩阵,执行和T相反的变换。

观察这种矩阵的一般形式,它非常有趣。让我们先用 "分块"形式写出前面用到的TRT-1

可以看出,仿射变换中增加的平移部分仅仅改变了4x4矩阵的最后一行,并没有影响到上面所包含的线性变换的3x3部分。

 

透视投影

学习透视投影最好的方法是将它和平行投影相比较。正交投影也称作平行投影,因为投影线都是平行的(投影线是指从原空间中的点到投影点的连线)。正交投影中的平行线如图9.3所示:

3D中的透视投影仍然是投影到2D平面上,但是投影线不再平行,实际上,它们相交于一点,该点称作投影中心。如图9.4所示:

因为投影中心在投影平面前面,投影线到达平面之前已经相交,所以投影平面上的图像是翻转的。当物体远离投影中心时,正交投影仍保持不变,但透视投影变小了。如图9.5所示:

图9.5中,右边的茶壶离投影平面更远,所以它的投影比离投影平面较近的那个茶壶小。这是一种非常重要的视觉现象,称作透视缩略。

 

小孔成像

透视投影在图形学中非常重要,因为它是人类视觉系统的模型。实际上,人类视觉系统远比这复杂,因为我们有两只眼睛,而且对于每只眼睛,投影表面(视网膜)不是一个平面。所以,让我们来看一个简单些的例子----小孔成像。小孔成像系统就是一个盒子,一侧上有小孔,光线穿过小孔照射到另一侧的背面,那里就是投影平面。如图9.6所示:

图9.6中,盒子左面和右面是透明的,以使你能看见盒子内部。注意盒子内部的投影是倒着的,这是因为光线(投影线)已经在小孔处(投影中心)相交了。

让我们探索小孔成像背后的几何原理。设想一个3D坐标系,它的原点在投影中心,z轴垂直于投影平面,x和y轴平行于投影平面。如图9.7所示:

让我们看看能否计算出任意点p通过小孔投影到投影平面上的坐标p'。首先,需要知道小孔到投影平面的距离,设为d。因此,投影平面为z=-d。现在,从另一个角度来看问题,求出新的y。如图9.8所示。

由相似三角形得到:

注意小孔成像颠倒了图像,pypy'的符号相反。px'的值可通过类似的方法求得:

所有投影点的z值都是相同的:-d。因此,点p通过原点向平面z=-d投影的结果如公式9.11所示:

在实际应用中,负号会带来不必要的复杂性。所以将投影平面移到投影的前面(也就是说,平面z=d),如图 9.9所示:

当然,这对于实际的小孔成像是不可能的。因为设置小孔的目的就是使光线只能通过小孔,但在计算机数学世界中,可以不理会这些规定。如你所愿,将投影平面移到投影中心前面,烦人的负号消失了,如公式9.12所示:

 

使用4x4矩阵进行透视投影

从4D到3D的变换就意味着除法运算,因此我们可以利用4x4阶矩阵来编写代码,以实现透视投影。基本思想是提出一个关于p'的公式,其中的x、y、z有公分母,然后构造一个4x4矩阵,使w与这个公分母相等。这里我们假设初始点处有w=1。

先对3D形式表达的p'公式变形,可以得到:

将4D齐次向量变换到3D时,要用4D向量除以w,反推可知p'的4D形式为:

[x  y   z  z/d]

因此我们需要一个4x4矩阵,它可接收一个奇异的齐次向量。该向量的形式为[x, y, z, 1],然后将其变换为上述形式。这样的矩阵如公式9.13所示:

这样就得到了一个4x4投影矩阵,有几个需要注意的地方:

(1)乘以这个矩阵并没有进行实际的透视投影变换,它只是计算出合适的分母。投影实际发生在从4D向3D变换时。

(2)存在多种变换。例如,将投影平面放在z=0处而投影中心在[0, 0, -d],这将导致一个不同的公式。

(3)这里看起来比较复杂,似乎只需要简单地除以z,不必劳烦矩阵。那么为什么要使用齐次矩阵呢?第一,4x4矩阵提供了一个方法将投影表达为变换,这样就能和其他变换相连接;第二,使得投影到不平行于坐标轴的平面变得可行。实际上,我们不需要齐次坐标做任何运算,但4x4矩阵提供了一种简洁的方法表达和操纵投影变换。

(4)实际的图形几何管道中的投影矩阵不像这里导出的那样,还有许多重要的细节需要考虑。如用以上矩阵对向量进行变换后,z值实际上被舍弃了,而很多图形系统的z缓冲用到了该值。

4D向量和4x4矩阵不过是对3D运算的一种方便的记忆而已。

 

4D齐次空间

4D向量有4个分量,前3个是标准的x,y和z分量,第4个是w,有时称作齐次坐标。

为了理解标准3D坐标是怎样扩展到4D坐标的,让我们先看一下2D中的齐次坐标,它的形式为(x, y, w)。想象在3D中w=1处的标准2D平面,实际的2D点(x, y)用齐次坐标表示为(x, y, 1),对于那些不在w=1平面上的点,则将它们投影到w=1平面上。所以齐次坐标(x, y, w) 映射的实际2D点为(x/w, y/w)。如图9.2所示:

因此,给定一个2D点(x, y),齐次空间中有无数多个点与之对应。所有点的形式都为(kx, ky, k),k≠0。这些点构成一条穿过齐次原点的直线。

当w=0时,除法未定义,因此不存在实际的2D点。然而,可以将2D齐次点(x, y, 0)解释为"位于无穷远的点",它描述了一个方向而不是一个位置。

4D坐标的基本思想相同,实际的3D点被认为是在4D中w=1"平面"上。4D点的形式为(x, y, z, w),将4D点投影到这个"平面"上得到相应的实际3D点(x/w, y/w, z/w)。w=0时4D点表示"无限远点",它描述了一个方向而不是一个位置。

 

4 X 4 平移矩阵

3x3变换矩阵表示的是线性变换,不包括平移。因为矩阵乘法的性质,零向量总是变换成零向量。因此,任何能用矩阵乘法表达的变换都不包含平移。这很不幸,因为矩阵乘法和它的逆是一种非常方便的工具,不仅可以用来将复杂的变换组合成简单的单一变换,还可以操纵嵌入式坐标系间的关系。如果能找到一种方法将3x3变换矩阵进行扩展,使它能处理平移,这将是一件多么美妙的事情啊。4x4矩阵恰好提供了一种数学上的"技巧"使我们能够做到这一点。

暂时假设w总是等于1。那么,标准3D向量[x, y, z]对应的4D向量为[x, y, z, 1]。任意3x3变换矩阵在4D中表示为:

任意一个形如[x, y, z, 1]的向量乘以上面形式的矩阵,其结果和标准的3x3情况相同,只是结果是用w=1的4D向量表示的:

现在,到了最有趣的部分。在4D中,仍然可以用矩阵乘法来表达平移,如公式9.10所示,而在3D中是不可能的:

记住,即使是在4D中,矩阵乘法仍然是线性变换。矩阵乘法不能表达4D中的"平移",4D零向量也将总是被变换成零向量。这个技巧之所以能在3D中平移点是因为我们实际上是在切变4D空间。与实际3D空间相对应的4D中的"平面"并没有穿过4D中的原点。因此,我们能通过切变4D空间来实现3D中的平移。

设想没有平移的变换后接一个有平移的变换会发生什么情况呢?设R为旋转矩阵(实际上,R还能包含其他的3D线性变换,但现在假设R只包含旋转),T为形如公式9.10的变换矩阵:

将向量v先旋转再平移,新的向量v'计算如下:

v' = vRT

注意,变换的顺序是非常重要的。因为我们使用的是行向量,变换的顺序必须和矩阵乘法的顺序相吻合(从左到右),先旋转后平移。

和3x3矩阵一样,能将两个矩阵连接成单个矩阵,记作矩阵M,如下:

= RT

v' = vRT = v(RT) = vM

观察M的内容:

注意到,M的上边3x3部分是旋转部分,最下一行是平移部分。最右一列为[0, 0, 0, 1]T。逆向利用这些信息,能将任意4x4矩阵分解为线性变换部分和平移部分。将平移向量[△x, △y, △z]记作t,则M可简写为:

接下来看w=0所表示的 "无穷远点"。它乘以一个由"标准"3x3变换矩阵扩展成的4x4矩阵(不包含平移),得到:

换句话说,当一个形如[x, y, z, 0]的无穷远点乘以一个包含旋转、缩放等的变换矩阵,将会发生预期的变换。结果仍是一个无穷远点,形式为[x, y, z, 0]。

一个无穷远点经过包含平移的变换可得到:

注意到结果是一样的(和没有平移的情况相比)。换句话说,4D向量中的w分量能够"开关"  4x4 矩阵的平移部分。这个现象是非常有用的,因为有些向量代表“位置”,应当平移,而有些向量代表“方向”,如表面的法向量,不应该平移。从几何意义上说,能将第一类数据当作"点",第二类数据当作"向量".

使用4x4矩阵的一个原因是4x4变换矩阵能包含平移。当我们仅为这个目的使用4x4矩阵时,矩阵的最后一列总是[0, 0, 0, 1]T。既然是这样,为什么不去掉最后一列而改用4x3矩阵呢?根据线性代数法则,由于多种原因,4x3矩阵不符合我们的需求,如下:

(1)不能用一个4x3矩阵乘以另一个4x3矩阵。

(2)4x3矩阵没有逆矩阵,因为它不是一个方阵。

(3)一个4D向量乘以4x3矩阵时,结果是一个3D向量。

为了严格遵守线性代数法则,我们加上了第4列。当然在代码中,可以不受代数法则的约束。

正交矩阵的运算法则

若方阵M是正交的,则当且仅当M与它转置矩阵MT的乘积等于单位矩阵,见公式9.8:

矩阵乘以它的逆等于单位矩阵:M M-1 = I

所以,如果一个矩阵是正交的,那么它的转置等于它的逆:

这是一条非常有用的性质,因为在实际应用中经常需要计算矩阵的逆,而3D图形计算中正交矩阵出现又是如此频繁。比如旋转和镜像矩阵是正交的,如果知道矩阵是正交的,就可以完全避免计算逆矩阵了,这也将大大减少计算量。

 

正交矩阵的几何解释

正交矩阵对我们非常有用,因为很容易计算它的逆矩阵。但怎样知道一个矩阵是否正交,以利用它的性质呢?

很多情况下,我们可以提前知道矩阵是如何建立的,甚至了解矩阵是仅包含旋转、镜像呢,还是二者皆有(记住:旋转和镜像矩阵是正交的)。这种情况非常普遍。

如果无法提前清楚矩阵的某些情况呢?换句话说,对于任意矩阵M,怎样检测它是否正交?为了做到这一点,让我们从正交矩阵的定义开始,以3x3阶矩阵为例。设M是3x3矩阵,根据定义,当且仅当 M MT = IM是正交的。它的确切含义如下:

现在做一些解释:

(1)当且仅当一个向量是单位向量时,它与自身的点积结果是1。因此,仅当r1r2r3是单位向量时,第1、5、9式才能成立。

(2)当且仅当两个向量是互相垂直时,它们的点积为0。因此,仅当r1r2r3互相垂直时其他等式才成立。

所以,若一个矩阵是正交的,它必须满足下列条件:

矩阵的每一行都是单位向量,矩阵的所有行互相垂直。

对矩阵的列也能得到类似的条件,这使得以下结论非常清楚:如果M是正交的,则MT也是正交的。

计算逆矩阵时,仅在预先知道矩阵是正交的情况下才能利用正交性的优点。如果预先不知道,那么检查正交性经常是浪费时间。即使在最好的情况下,先检查正交性以确定矩阵是否正交再进行转置,和一开始就进行求逆运算也将耗费同样多的时间。而如果矩阵不是正交,那么这种检查完全是浪费时间。

注意,有一个术语上的差别可能会导致轻微的混淆。线性代数中,如果一组向量互相垂直,这组向量就被认为是正交基(orthogonal basis)。它只要求所有向量互相垂直,并不要求所有向量都是单位向量。如果它们都是单位向量,则称它们为标准正交基(orthogonal basis)。这里所讲的正交矩阵的行或列向量都是指标准正交基向量(orthogonal basis vectors),所以由一组正交基向量构造的矩阵并不一定是正交矩阵(除非基向量是标准正交的)。

 

矩阵正交化

有时可能会遇到略微违反了正交性的矩阵。例如,可能从外部得到了坏数据,或者是浮点运算的累积错误(称作”矩阵爬行“)。这些情况下,需要做矩阵正交化,得到一个正交矩阵,这个矩阵要尽可能地和原矩阵相同(至少希望是这样)。

构造一组正交基向量(矩阵的行)的标准算法是施密特正交化。它的基本思想是,对每一行,从中减去它平行于已处理过的行的部分,最后得到垂直向量。

以3x3矩阵为例,和以前一样,用r1r2r3代表3x3阶矩阵M的行。正交向量组r1'r2'r3'的计算如公式9.9所示:

现在r1'r2'r3'互相垂直了,它们是一组正交基。当然,它们不一定是单位向量。构造正交矩阵需要使用标准正交基,所以必须标准化这些向量。注意,如果一开始就进行标准化,而不是在第2步中做,就能避免所有除法了。

施密特正交化是有偏差的,这取决于基向量列出的顺序。一个明显的例子是,r1总不用改变。该算法的一个改进是不在一次正交化过程中将整个矩阵完全正交化。而是选择一个小的因子k,每次只减去投影的k倍,而不是一次将投影全部减去。改进还体现在,在最初的轴上也减去投影。这种方法避免了因为运算顺序不同带来的误差。算法总结如下:

该算法的每次迭代都会使这些基向量比原来的基向量更为正交化,但可能不是完全正交的,多次重复这个过程,最终将得到一组正交基。要得到完美的结果,就得选择一个适当的因子k并迭代足够多次(如:10次)。接着,进行标准化,最后就会得到一组正交基。

矩阵的逆

另外一种重要的矩阵运算是矩阵的求逆,这个运算只能用于方阵。

 

运算法则

方阵M的逆,记作M-1,也是一个矩阵。当MM-1相乘时,结果是单位矩阵。表示为公式9.6的形式:

并非所有的矩阵都有逆。一个明显的例子是若矩阵的某一行或列上的元素都为0,用任何矩阵乘以该矩阵,结果都是一个零矩阵。如果一个矩阵有逆矩阵,那么称它为可逆的或非奇异的。如果一个矩阵没有逆矩阵,则称它为不可逆的或奇异矩阵。奇异矩阵的行列式为0,非奇异矩阵的行列式不为0,所以检测行列式的值是判断矩阵是否可逆的有效方法。此外,对于任意可逆矩阵M,当且仅当v=0时,vM=0

M的”标准伴随矩阵“记作”adjM“,定义为M的代数余子式矩阵的转置矩阵。下面是一个例子,考虑前面给出的3x3阶矩阵M

计算M的代数余子式矩阵:

M的标准伴随矩阵是代数余子式矩阵的转置:

一旦有了标准伴随矩阵,通过除以M的行列式,就能计算矩阵的逆。

其表示如公式9.7所示:

例如为了求得上面矩阵的逆,有:

当然还有其他方法可以用来计算矩阵的逆,比如高斯消元法。很多线性代数书都断定该方法更适合在计算机上实现,因为它所使用的代数运算较少,这种说法其实是不正确的。对于大矩阵或某些特殊矩阵来说,这也许是对的。然而,对于低阶矩阵,比如几何应用中常见的那些低阶矩阵,标准伴随矩阵可能更快一些。因为可以为标准伴随矩阵提供无分支(branchless)实现,这种实现方法在当今的超标量体系结构和专用向量处理器上会更快一些。

矩阵的逆的重要性质:

 

几何解释

矩阵的逆在几何上非常有用,因为它使得我们可以计算变换的”反向“或”相反“变换 ---- 能”撤销“原变换的变换。所以,如果向量v用矩阵M来进行变换,接着用M的逆M-1进行变换,将会得到原向量。这很容易通过代数方法验证:

向量几何在游戏编程中的使用

-Twinsen编写

  Andre Lamothe说:“向量几何是游戏程序员最好的朋友”。一点不假,向量几何在游戏编程中的地位不容忽视,因为在游戏程序员的眼中,显示屏幕就是一个坐标系,运动物体的轨迹就是物体在这个坐标系曲线运动结果,而描述这些曲线运动的,就是向量。使用向量可以很好的模拟物理现象以及基本的AI。

<1>简单的2-D追踪
Andre Lamothe说:“向量几何是游戏程序员最好的朋友”。一点不假,向量几何在游戏编程中的地位不容忽视,因为在游戏程序员的眼中,显示屏幕就是一个坐标系,运动物体的轨迹就是物体在这个坐标系曲线运动结果,而描述这些曲线运动的,就是向量。使用向量可以很好的模拟物理现象以及基本的AI。

现在,先来点轻松的,复习一下中学知识
向量v
(用粗体字母表示向量)也叫矢量,是一个有大小有方向的量。长度为1的向量称为单位向量,也叫幺矢,这里记为E。长度为0的向量叫做零向量,记为0,零向量没有确定方向,换句话说,它的方向是任意的。
一、向量的基本运算


1、向量加法:a+b等于使b的始点与a的终点重合时,以a的始点为始点,以b的终点为终点的向量。
2、向量减法:a-b等于使b的始点与a的始点重合时,以b的终点为始点,以a的终点为终点的向量。
3、 数量乘向量:k*a,k>0时,等于a的长度扩大k倍;k=0时,等于0向量;k<0时,等于a的长度扩大|k|倍然后反向。
4、向量的内积(数量积、点积): a.b=|a|*|b|*cosA 等于向量a的长度乘上b的长度再乘上ab之间夹角的余弦。
   它的几何意义就是a的长度与ba上的投影长度的乘积,或者是b的长度与ab上投影长的乘积,它是一个标量,而
且可正可负。因此互相垂直的向量的内积为0。


5、向量的矢积(叉积): a x b = |a|*|b|*sinA*v = c, |a|是a的长度,|b|是b的长度,A是ab之间的锐夹角,v是与a,b所决定的平面垂直的幺矢,即axbab都垂直。a,b,c构成右手系,即右手拇指伸直,其余四指按由ab的锐角蜷曲,此时拇指所指方向就是c的方向。因此axb!=bxa,bxa是手指朝ba的锐角蜷曲时,拇指指向的方向,它和c相反,即-ca x b的行列式计算公式在左右手坐标系下是不同的,如上图所示。两个向量的矢积是一个向量。
6、正交向量的内积:互相垂直的两个向量是正交的,正交向量的内积为零。a.b = |a|.|b|*cos(PI/2) = |a|.|b|*0 = 0。

二、向量的性质
没有下面的这些性质做基础,我们后面向量技巧的推导将无法进行。

1) a + b = b + a
2) (a + b) + c = a + (b + c)
3) a + 0 = 0 + a = a
4) a + (-a) = 0
5) k*(l*a) = (k*l)*a = a*(k*l)
6) k*(a + b) = k*a + k*b
7) (k + l)*a = k*a + l*a
8) 1*a = a

9) a.b = b.a
10)a.(b + c) = a.b + a.c
11)k*(a.b) = (k*a).b = a.(k*b)
12)0.a = 0
13)a.a = |a|^2

三、自由向量的代数(分量)表示
1、向量在直角坐标中的代数表示方法:
a=(x,y)


其中x,y分别是向量在x轴和y轴上的分量。任何一个在直角坐标轴上的分量为(x,y)的向量都相等。比如上图中的每个向量都表示为(-2,1)。
或者写成a=x*i+y*j,ij的线性组合,这里i是x轴方向的单位向量(1,0),j是y轴方向的单位向量(0,1),因此i正交于j。任意一个2-D向量都可以表成ij的线性组合。

|i| = |j| = 1

2、向量的代数(分量)表示的运算:
向量加法分量表示:a+b=(xa,ya)+(xb,yb)=(xa+xb,ya+yb)
向量减法分量表示:a-b=(xa,ya)-(xb,yb)=(xa-xb,ya-yb)
向量的内积(数量积、点积)分量表示:
a.b
=(xa * i + ya * j).(xb * i + yb * j)
= xa * i * xb * i + xa * i * yb * j + ya * j * xb * i + ya * j * yb * j
=(xa * xb) * (i * i) + (xa * yb) * (i * j) + (xb * ya) * (i * j) + (ya * yb) * (j * j)
= xa * xb + ya * yb

3、向量长度(模)的计算以及单位化(归一化):
a=(x,y),则
|a| = |(x,y)| = |x*i + y*j| = sqrt(x^2*i^2 + y^2*j^2) = sqrt(x^2 + y^2),这里sqrt是开平方符号。
a的单位向量为a/|a|,即(x,y)/sqrt(x^2 + y^2)。

四、简单的2-D追踪

现在,有了向量的基本知识,我们就可以分析一个常见的问题-屏幕上一点到另一点的追踪,其实这一问题也可理解为画线问题,画线的算法有很多:DDA画线法、中点画线法以及高效的Bresenham算法。但这些算法一般只是画一些两端固定的线段时所使用的方法,再做一些动态的点与点之间的跟踪时显得不很灵活。使用向量的方法可以很好的解决此类问题。
现在假设你正在编写一个飞行射击游戏,你的敌人需要一种很厉害的武器-跟踪导弹,这种武器在行进的同时不断的修正自己与目标之间的位置关系,使得指向的方向总是玩家,而不论玩家的位置在哪里,这对一个水平不高的玩家(我?)来说可能将是灭顶之灾,玩家可能很诧异敌人会拥有这么先进的秘密武器,但对于你来说只需要再程序循环中加入几行代码
,它们的原理是向量的单位化和基本向量运算。

首先我们要知道玩家的位置(x_player, y_player),然后,我们的导弹就可以通过计算得到一个有初始方向的速度,速度的方向根据玩家的位置不断修正,它的实质是一个向量减法的计算过程。速度的大小我们自己来设置,它可快可慢,视游戏难易度而定,它的实质就是向量单位化和数乘向量的过程。具体算法是:导弹的更新速度(vx_missile, vy_missile) = 玩家的位置(x_player, y_player) - 导弹的位置(x_missile, y_missile),然后再对(vx_missile, vy_missile)做缩小处理,导弹移动,判断是否追到玩家,重新更新速度,缩小...

看一下这个简单算法的代码:

// 假设x_player,y_player是玩家位置分量
// x_missile,y_missile是导弹位置分量
// xv_missile,yv_missile是导弹的速度分量
// 让我们开始吧!
float n_missile ; // 这是玩家位置与导弹位置之间向量的长度
float v_rate ; // 这是导弹的速率缩放比率
// 计算一下玩家与导弹之间的位置向量
xv_missile = x_player-x_missile ; // 向量减法,方向由导弹指向玩家,x分量
yv_missile = y_player-y_missile ; // y分量
// 计算一下它的长度
n_missile = sqrt( xv_missile*xv_missile + yv_missile*yv_missile ) ;
// 归一化导弹的速度向量:
xv_missile /= n_missile ;
yv_missile /= n_missile ;
// 此时导弹的速率为1,注意这里用速率。
// 导弹的速度分量满足xv_missile^2+yv_missile^2=1
// 好!现在导弹的速度方向已经被修正,它指向玩家。
// 由于现在的导弹速度太快,为了缓解一下紧张的气氛,我要给导弹减速
v_rate = 0.2f ; // 减速比率
xv_missile *= v_rate ; // 这里的速率缩放比率,你可以任意调整大小
yv_missile *= v_rate ; // 可以加速:v_rate大于1;减速v_rate大于0小于1,这里就这么做!
// 导弹行进!导弹勇敢的冲向玩家!
x_missile += xv_missile ;
y_missile += yv_missile ;
// 然后判断是否攻击成功
现在,你编写的敌人可以用跟踪导弹攻击玩家了。你也可以稍加修改,变为直线攻击武器。这样比较普遍。
基本的跟踪效果用向量可以很好的模拟。
此时,我们只用到了所述向量知识的很少的一部分。其他的知识会慢慢用到游戏中。这次先介绍到这里。
下次我将说说利用向量模拟2-D物体任意角度返弹的技巧:)但是!别忘了复习一下向量的基础知识,我们要用到它们。

<2>2-D物体任意角度的反弹

第一次我说了一下向量知识的基础内容和一点使用技巧,浅显的展示了它在游戏编程中的作用。这次深入一些,充分利用向量的性质模仿一个物理现象。

首先,我要介绍一下将要使用的两个基本但非常重要的技巧。
一、求与某个向量a正交的向量b


根据向量内积的性质以及正交向量之间的关系,有:
a=(xa,ya),b=(xb,yb)
a.b = 0
=> xa*xb + ya*yb = 0
=> xa*xb = -ya*yb
=> xa/-ya = yb/xb
=> xb = -ya , yb = xa 或 xb = ya , yb = -xa
则向量(xa,ya)的正交向量为(xb,yb)=(-ya,xa)
比如上图中,向量(2,3)的逆时针旋转90度的正交向量是(-3,2),顺时针旋转90度的正交向量为(3,-2)。
这样,任给一个非零向量(x,y),则它相对坐标轴逆时针转90度的正交向量为(-y,x),顺时针转90度的正交向量为(y,-x)。
二、计算一个向量b与另一向量a共线的两个相反的投影向量

我们看一下上面的图,很明显,cosA(A=X)关于y轴对称,是偶函数,因此cosA = cos(-A),
又因为cosA是周期函数,且周期是2*PI,则有cos(A+2*PI) = cosA = cos(-A) = cos(-A+2*PI),
则根据cosA = cos(2*PI-A)以及a.b = |a|*|b|*cosA,有
a.b = |a|*|b|*cosA = |a|*|b|*cos(2*PI-A)

现在,根据上图,就有a.b = |a|*|b|*cosA = |a|*|b|*cos(2*PI-A) = ax*bx + ay*by

按照这个规则,当上面的bc的模相等时,有|a|*|b| = |a|*|c|,进一步的,当它们与a的夹角A = B时,就有
a.b = |a|*|b|*cosA = |a|*|c|*cosB = a.c ,相应的有
a.b = |a|*|b|*cosA = |a|*|b|*cos(2*PI-A) = |a|*|c|*cosB = |a|*|c|*cos(2*PI-B) = a.c 也就是
ax*bx + ay*by = ax*cx + ay*cy
我们还注意到在一个周期内,比如在[0,2*PI]中,cosA有正负两种情况,分别是:在(0,PI/2)&(3*PI/2, 2*PI)为正,在(PI/2,3/2*PI)为负。好,知道了这件事情之后,再看a.b = |a|*|b|*cosA,|a|和|b|都为正,所以a.b的正负性就由cosA决定,换句话说,a.b与它们夹角A的余弦cos有相同的符号。所以,还看上面的图,我们就有:
1)当A在(0, PI/2)&(3*PI/2, 2*PI)中,此时2*PI-A在(-PI/2,0)&(0, PI/2)中,a.b为正
2)当A在(PI/2, 3*PI/2)中,此时2*PI-A也在(PI/2, 3*PI/2)中,a.b为负

现在我们再来看一下同模相反(夹角为PI)向量bb'与同一个向量a的两个内积之间有什么关系。
首先B + B'= 2*PI - PI = PI,所以有b = -b', b' = -b,即

(bx, by) = (-b'x, -b'y) = -(b'x, b'y)
(b'x, b'y) = (-bx, -by) = -(bx, by)
所以
a.b =
(ax, ay) . (bx, by) = (ax, ay) . -(b'x, b'y) = a.-b'= -(a.b')
a.b'= (ax, ay) . (b'x, b'y) = (ax, ay) . -(bx, by) = a.-b = -(a.b)
我们看到,一个向量b的同模相反向量b'与向量a的内积a.b',等于ba的内积的相反数-(a.b)

好,有了上面的基础,我们就可以求一个向量b与另一向量a共线的两个相反的投影向量cc'了。

要求ba上的投影向量c,我们可以用一个数乘上一个单位向量,这个单位向量要和a方向一至,我们记为a1。而这个数就是ba上的投影长。
先来求单位向量a1,我们知道它就是向量a乘上它自身长度的倒数(数乘向量),它的长度我们
可以求出,就是m = sqrt(ax^2 + ay^2),所以a1就是(ax/m, ay/m),记为(a1x, a1y)。

再求投影长/c/(注意//与||的区别,前者是投影长,可正可负也可为零,后者是实际的长度,衡为非负)。 根据内积的几何意义:一个向量b点乘另一个向量a1,等于ba1上投影长与a1的长的乘积。那我们要求ba上的投影长,就用它点乘a的单位向量a1就可以了,因为单位向量的长度为1,b的投影长/c/乘上1还等于投影长自身,即:
/c/ = b.a1 = (bx, by) . (a1x, a1y) = bx * a1x + by * a1y
好,我们得到了c的投影长,现在就可以求出c:

c = /c/*a1 = ( (bx * a1x + by * a1y)*a1x, (bx * a1x + by * a1y)*a1y )
总结一下,就是c = (b.a1)*a1。

我们看到,ba1的夹角在(0, PI/2)之间,因此它们的点积/c/是个正值。因此当它乘a1之后,得到向量的方向就是a1的方向。
现在来看b',它是b的同模相反向量,它和a1的夹角在(PI/2, 3*PI/2)之间,因此b'点乘a1之后得到/c'/是个负值,它再乘a1,得到向量的方向和a1相反。我们知道一个向量b的同模相反向量b'与向量a的内积a.b',等于ba的内积的相反数-(a.b)。因此,/c'/ = -/c/,也就是说,它们的绝对值相等,符号相反。因此它们同乘一个a1,得到的的两个模相等向量cc'共线。
让我们把它完成:
(b'.a1) = -(b.a1)
=> -(b'.a1) = (b.a1), 好,代入c = (b.a1)*a1,得到

c = -(b'.a1)*a1
=> (b'.a1)*a1 = -c = c'
c = ( b . a1 )
* a1 = (-b'. a1) * a1
c'= ( b'. a1 )
* a1 = (-b . a1) * a1

至此为止,我们得出结论:当一个向量b与另一个向量a的夹角在(0, PI/2)&(3*PI/2, 2*PI)之间,它在a方向上的投影向量c就是c = ( b . a1 ) * a1,其中a1a的单位向量;它在a相反方向的投影向量c'c'= ( b'. a1 ) * a1,其中向量b'b的同模相反向量。
相反的,也可以这样说:当一个向量b'与另一个向量a的夹角在(PI/2, 3*PI/2)之间,它在a相反方向上的投影向量c'
c'= ( b'. a1 ) * a1,其中 a1a的单位向量;它在a方向上的投影向量cc = ( b . a1 ) * a1。其中向量bb'的同模相反向量。

特别的,点乘两个单位向量,得到它们夹角的余弦值:
E.E = |E|*|E|*cosA = 1*1*cosA = cosA
好了,可完了。 现在就可以看一下
三、使用向量模拟任意角度反弹的原理

根据初等物理,相互接触的物体在受到外力具有接触面相对方向相对运动趋势的时候,接触面会发生形变从而产生相互作用的弹力。
弹力使物体形变或形变同时运动形式发生改变。在知道了这件事情之后,我们开始具体讨论下面这种情况:

矩形框和小球碰撞,碰撞时间极短,墙面无限光滑从而碰撞过程没有摩擦,碰撞时间极短,没有能量损失...总之是一个理想的物理环境。我们在这种理想环境下讨论,小球与墙面发生了完全弹性碰撞,且入射角和反射角相等:A=A',B=B',C=C',...。虚线是法线,它和墙面垂直。小球将在矩形框中永无休止的碰撞下去,且每次碰撞过程中入射角和反射角都相等。
我们再具体点,现在假设上面那个矩形墙壁的上下面平行于x轴,左右面平行于y轴。这样太好了,我们在编写程序的时候只要判断当球碰到上下表面的时候将y方向速度值取返,碰到左右表面时将x方向速度值取返就行了,这种方法常常用在简单物理模型和规则边界框的游戏编程上,这样可以简化很多编程步骤,编写简单游戏时可以这样处理。可事实不总是像想向中的那么好。如果情况像下面这样:

虽然在碰撞过程中入射角仍然等于反射角,但是边界的角度可没那么“纯”了,它们的角度是任意的,这样就不能简单的将x方向或者y方向的速度取返了,我们要另找解决办法。
我们现在的任务是:已知物体的速度向量S和边界向量b,求它的反射向量F。我们先来看一下在碰撞过程中都有哪些向量关系:

b是障碍向量,S是入射速度向量,F是反射速度向量,也就是我们要计算的向量。A是入射角度,A'是反射角度,A=A'。Nb的法向量,即N垂直于bn是与N共线的向量,n'N方向的单位向量。T是垂直于N的向量。根据向量加法,现在有关系:
(1) S + n = T
(2) n + T = F
合并,得
F = 2*T - S
我们已经找到了计算F的公式了。这里S是已知的,我们要计算一下T,看(1)式:
T = S + n
要计算TS是已知的,就要计算一下n。我们知道,nSN方向上投影得到的,S已知所以要得到n就要再计算一下N,而N又是和b垂直的。还记得刚才我们导出的使用向量的两个技巧吧,这里我们都要用到:
1、任给一个非零向量(x,y),则它相对坐标轴逆时针转90度的垂直向量为(-y,x),顺时针转90度垂直向量为(y,-x)。
2、当一个向量b与另一个向量a的夹角在(0, PI/2)&(3*PI/2, 2*PI)之间,它在a方向上的投影向量c就是c = ( b . a1 ) * a1,其中a1a的单位向量;它在a相反方向的投影向量c'c'= ( b'. a1 ) * a1,其中向量b'b的同模相反向量。
我们知道了b,用技巧1可以计算出N。然后归一化N计算出n',再用技巧2,这里Sn'之间的夹角在(PI/2, 3*PI/2)中,因此要想用c = ( b. a1 ) * a1,必须要使b = -S,a1=n'。这样就计算出了n。然后根据上面的(1)式计算出T,好了,有了TF = 2*T - S ,你就拥有了一切!
计算出的F就是物体碰撞后的速度向量,在2-D中它有两个分量x和y,3-D中有x,y,z三个分量。这里也证明了使用向量的一个好处就是在一些类似这样关系推导过程中不用去考虑坐标问题,而直接的用简单的向量就可以进行。
这里注意我们的障碍向量b在实际的编程中是用障碍的两个端点坐标相减计算出的,计算的时候不需要考虑相减的顺序问题。因为虽然用不同的相减顺序得到b的方向相反,且计算得到的单位法向量n'方向也相反(看上图的虚线部分),但是当用-S去点乘单位法向量n'之后得到的值也是相反的,它有一个自动的调节功能:现在假设以b为界,S一侧为正方向。则如果单位法向量n'是正方向,与-S点积值也是正,正的n'再乘正点积得正的n;如果单位法向量为负方向,与-S点积值也为负值,负的n'再乘负的点积得到的n为正方向。总之n的方向是不变的,算出的F当然也是不变的。
四、编码实现它
现在我想编码实现它,但之前有一点我想说一下,可能读者已经想到了,在反弹之前我们要先判断什么时候开始反弹,也就是什么时候碰撞,这是一个碰撞检测问题,本来这是我们应该先要解决的问题,但我想把它放到下一次在具体说,所以这里的编码省略碰撞检测的一步,直接计算反弹速度向量!目的是把上述理论迅速用到算法中去。

// 在游戏循环中
// 移动的物体简化为质点,位置是x=0.0f,y=0.0f
// 质点速度向量的分量是Svx=4.0f,Svy=2.0f
// 障碍向量是bx=14.0f-6.0f=8.0f,by=4.0f-12.0f=-8.0f
// 则障碍向量的垂直向量是Nx=-8.0f,Ny=-8.0f
// 这里可以加入碰撞检测
// 现在假设已经碰撞完毕,开始反弹计算!
// 计算N的长度
float lengthN = sqrt( Nx*Nx + Ny*Ny ) ;
// 归一化N为n'
float n0x = Nx / lengthN ; // n0x就是n'的x分量
float n0y = Ny / lengthN ; // n0y就是n'的y分量
// 计算n,就是S在N方向上的投影向量
// 根据b'= (-b.a1').a1',有n = (-S.n').n'
float nx = -(Svx*n0x+Svy*n0y)*n0x ; // n的x分量
float ny = -(Svx*n0x+Svy*n0y)*n0y ; // n的y分量
// 计算T
// T = S + n
float Tx = Svx + nx ; // T的x分量
float Ty = Svy + ny ; // T的y分量
// 有了T,有了F = 2*T - S,好了,你现在拥有一切了
// 计算F
float Fx = 2*Tx - Svx ; // F的x分量
float Fy = 2*Ty - Svy ; // F的y分量
// 现在已经计算出了反弹后的速度向量了
// 更新速度向量
Svx = Fx ;
Svy = Fy ;
// 质点移动
x+=Svx ;
y+=Svy ;
// 现在你就可以看到质点被无情的反弹回去了
// 而且是按照物理法则在理想环境下模拟
就是这么简单,一个物理现象就可以模拟出来,但是还不完善,只是针对直线障碍,且没有碰撞检测,下次分析一下后者,还是用向量的知识。这次先到这,See u next time!

<3>2-D边界碰撞检测
-Twinsen编写

-本人水平有限,疏忽错误在所难免,还请各位数学高手、编程高手不吝赐教
-我的Email-address: popyy@netease.com

一、使用向量进行障碍检测的原理

上次说了使用向量模拟任意角度的反弹,这次谈谈它的前提---障碍碰撞。
在游戏中进行障碍碰撞检测,基本思路是这样的:给定一个障碍范围,判断物体在这次移动后会不会进入这个范围,如果会,就发生碰撞,否则不发生碰撞。在实际操作中,是用物体的边界来判断还是其他部位判断完全取决于编程者。这时候,就可以从这个部位沿着速度的方向引出一条速度向量线,判断一下这条线段(从检测部位到速度向量终点)和障碍边界线有没有交点,如果有,这个交点就是碰撞点。

上面物体A,在通过速度向量移动之后将到达B位置。但是,这次移动将不会顺利进行,因为我们发现,碰撞发生了。碰撞点就在那个红色区域中,也就是速度向量和边界线的交点。 我们接下来的工作就是要计算这个交点,这是一个解线性方程组的过程,那么我们将要用到一样工具...
二、一个解线性方程组的有力工具---克兰姆(Cramer)法则

首先要说明一下的是,这个法则是有局限性的,它必须在一个线性方程组的系数行列式非零的时候才能够使用。别紧张,我会好好谈谈它们的。首先让我来叙述一下这个法则(我会试着让你感觉到这不是一堂数学课):
如果线性方程组:
A11*X1 + A12*X2 + ... + A1n*Xn = b1
A21*X1 + A22*X2 + ... + A2n*Xn = b2
...................................
An1*X1 + An2*X2 + ... + Ann*Xn = bn
的系数矩阵 A =
__               __
| A11 A12 ... A1n |
| A21 A22 ... A2n |
| ...............       |
| An1 An2 ... Ann |
--               --
的行列式 |A| != 0
线性方程组有解,且解是唯一的,并且解可以表示为:
X1 = d1/d , X2 = d2/d , ... , Xn = dn/d (这就是/A/=d为什么不能为零的原因)
这里d就是行列式/A/的值,dn(n=1,2,3...)是用线性方程组的常数项b1,b2,...,bn替换系数矩阵中的第n列的值得到的矩阵的行列式的值,即:

d1 =
| b1 A12 ... A1n |
| b2 A22 ... A2n |
| ..............       |
| bn An2 ... Ann |

d2 =
| A11 b1 ... A1n |
| A21 b2 ... A2n |
| ..............       |
| An1 bn ... Ann |

...

| A11 A12 ... b1 |

dn =
| A21 A22 ... b2 |
| ..............       |
| An1 An2 ... bn |

别去点击关闭窗口按钮!我现在就举个例子,由于我们现在暂时只讨论2-D游戏(3-D以后会循序渐进的谈到),就来个2-D线性方程组:
(1) 4.0*X1 + 2.0*X2 = 5.0
(2) 3.0*X1 + 3.0*X2 = 6.0
这里有两个方程,两个未知量,则根据上面的Cramer法则:
      | 4.0 2.0 |
d = | 3.0 3.0 | = 4.0*3.0 - 2.0*3.0 = 6.0 (2阶行列式的解法,'\'对角线相乘减去'/'对角线相乘)
       | 5.0 2.0 |
d1 = | 6.0 3.0 | = 5.0*3.0 - 2.0*6.0 = 3.0
       | 4.0 5.0 |
d2 = | 3.0 6.0 | = 4.0*6.0 - 5.0*3.0 = 9.0

X1 = d1/d = 3.0/6.0 = 0.5
X2 = d2/d = 9.0/6.0 = 1.5  
好了,现在就得到了方程组的唯一一组解。
是不是已经掌握了用Cramer法则解2-D线性方程组了?如果是的话,我们继续。
三、深入研究
这里的2-D障碍碰撞检测的实质就是判断两条线段是否有交点,注意不是直线,是线段,两直线有交点不一定直线上的线段也有交点。现在我们从向量的角度,写出两条线段的方程。

现在有v1v2两条线段,则根据向量加法:
v1e = v1b + s*v1
v2e = v2b + t*v2
v1bv2b分别是两线段的一端。s,t是两个参数,它们的范围是[0.0,1.0],当s,t=0.0时,v1e=v1b,v2e=v2b;当s,t=1.0时,v1ev2e分别是两线段的另一端。s,t取遍[0.0,1.0]则v1ev2e取遍两线段的每一点。
那么我们要判断v1v2有没有交点,就让v1e=v2e,看解出的s,t是不是在范围内就可以了:
v1e = v2e
=> v1b + s*v1 = v2b + t*v2
=> s*v1 - t*v2 = v2b - v1b
写成分量形式:
s*x_v1 - t*x_v2 = x_v2b - x_v1b
s*y_v1 - t*y_v2 = y_v2b - y_v1b
现在是两个方程式,两个未知数,则根据Cramer法则:

d =
| x_v1 -x_v2 |

| y_v1 -y_v2 |

=
| 4.0 -2.0 |

| 1.0 -3.0 |

= -10.0

d1 =
| x_v2b-x_v1b -x_v2 |

| y_v2b-y_v1b -y_v2 |

=
| 5.0 -2.0 |

| 2.0 -3.0 |  

= -11.0  

s = d1/d = -11.0/-10.0 = 1.1 > 1.0
现在s已经计算出来,没有在[0.0,1.0]内,所以两线段没有交点,从图上看很直观。t没有必要再计算了。所以是物体与障碍没有发生碰撞。如果计算出的s,t都在[0.0,1.0]内,则把它们带入原方程组,计算出v1e或者v2e,它的分量就是碰撞点的分量。

四、理论上的东西已经够多的了,开始写程序
我现在要写一个用于处理障碍碰撞检测的函数,为了测试它,我还准备安排一些障碍:

这是一个凸多边形,我让一个质点在初始位置(10,8),然后给它一个随机速度,这个随机速度的两个分速度在区间[1.0,4.0]内,同时检测是否与边界发生碰撞。当碰撞发生时,就让它回到初始位置,重新给一个随机速度。
// 首先我要记下凸多边形的边界坐标
float poly[2][8] = {
{ 6.0f , 2.0f , 4.0f , 8.0f , 14.0f , 18.0f , 14.0f , 6.0f } , // 所有点的x分量,最后一个点和第一个点重合
{ 2.0f , 6.0f , 10.0f , 14.0f , 12.0f , 8.0f , 4.0f , 2.0f } // 所有点的y分量
} ;
// 定义一些变量
float x,y ; // 这是质点的位置变量
float vx , vy ; // 质点的速度向量分量
// 好,开始编写碰撞检测函数
bool CollisionTest() { // 当发生碰撞时返回true,否则返回false

float s , t ; // 线段方程的两个参数
// 各个参量
float x_v1 , x_v2 , y_v1 , y_v2 ;
float x_v2b , x_v1b , y_v2b , y_v1b ;
for( int i = 0 ; i < 8-1 ; ++i ) { // 循环到倒数第二个点
// 障碍线段
x_v1 = poly[0][i+1]-poly[0][i] ;
y_v1 = poly[1][i+1]-poly[1][i] ;
// 物体速度向量
x_v2 = vx ;
y_v2 = vy ;
// 障碍向量初始点
x_v1b = poly[0][i] ;
y_v1b = poly[1][i] ;
// 物体位置
x_v2b = x ;
y_v2b = y ;
// 计算d,d1和d2
//    | x_v1 -x_v2 |  
//d = | y_v1 -y_v2 |
//     | x_v2b-x_v1b -x_v2 |
//d1 = | y_v2b-y_v1b -y_v2 |
//     | x_v1 x_v2b-x_v1b |
//d2 = | y_v1 y_v2b-y_v1b |
d = (x_v1*(-y_v2))-((-x_v2)*y_v1) ;
d1 = ((x_v2b-x_v1b)*(-y_v2))-((-x_v2)*(y_v2b-y_v1b)) ;
d2 = (x_v1*(y_v2b-y_v1b))-((x_v2b-x_v1b)*y_v1) ;
// 判断d是否为零
if( abs(d) < 0.001f ) // 如果等于零做近似处理,abs()用于求绝对值
d = 0.001f ;
// 计算参量s,t
s = d1/d ;
t = d2/d ;
// 判断是否发生碰撞
// 如果发生了就返回true
if( 0.0f <= s && 1.0f >= s && 0.0f <= t && 1.0f >= t )
return true ;
} // for( int i = 0 ; i < 8-1 ; ++i )
// 没有发生碰撞,返回false
return false ;
} // end of function
// 现在对函数做测试
// 初始化质点
x = 10.0f , y = 8.0f ;
vx = vy = (float)(rand()%4+1) ;
// 进入主循环中
// 假设现在已经在主循环中
if( CollisionTest() ) { // 如果物体与质点发生碰撞
x = 10.0f , y = 8.0f ;
vx = vy = (float)(rand()%4+1) ;
}
// 质点移动
x+=vx ;
y+=vy ;
现在你就可以结合上次的讨论模拟一个完整的理想物理情景:一个物体在不规则障碍中移动、反弹,永不停息...除非...
至此为止我们讨论了2-D游戏的障碍碰撞检测以及它的编程实现,在此过程中涉及到了线性代数学的知识,以后随着深入还会不断的加入更多的数学、物理知识。

<4>2-D物体间的碰撞响应
这次我要分析两个球体之间的碰撞响应,这样我们就可以结合以前的知识来编写一款最基本的2-D台球游戏了,虽然粗糙了点,但却是个很好的开始,对吗?

一、初步分析

中学时候上物理课能够认真听讲的人(我?哦,不包括我)应该很熟悉的记得:当两个球体在一个理想环境下相撞之后,它们的总动量保持不变,它们的总机械能也守恒。但这个理想环境是什么样的呢?理想环境会不会影响游戏的真实性?对于前者我们做出在碰撞过程中理想环境的假设:

1)首先我们要排除两个碰撞球相互作用之外的力,也就是假设没有外力作用于碰撞系统。
2)假设碰撞系统与外界没有能量交换。
3)两个球体相互作用的时间极短,且相互作用的内力很大。

有了这样的假设,我们就可以使用动量守恒和动能守恒定律来处理它们之间的速度关系了,因为1)确保没有外力参与,碰撞系统内部动量守恒,我们就可以使用动量守恒定律。2)保证了我们的碰撞系统的总能量不会改变,我们就可以使用动能守恒定律。3)两球发生完全弹性碰撞,不会粘在一起,没有动量、能量损失。
而对于刚才的第二个问题,我的回答是不会,经验告诉我们,理想环境的模拟看起来也是很真实的。除非你是在进行科学研究,否则完全可以这样理想的去模拟。

现在,我们可以通过方程来观察碰撞前后两球的速度关系。当两球球心移动方向共线(1-D处理)时的速度,或不共线(2-D处理)时共线方向的速度分量满足:

(1)m1 * v1 + m2 * v2 = m1 * v1' + m2 * v2' (动量守恒定律)
(2)1/2 * m1 * v1^2 + 1/2 * m2 * v2^2 = 1/2 * m1 * v1'^2 + 1/2 * m2 * v2'^2 (动能守恒定律)

这里m1和m2是两球的质量,是给定的,v1和v2是两球的初速度也是我们已知的,v1'和v2'是两球的末速度,是我们要求的。好,现在我们要推导出v1'和v2'的表达式:

由(1),得到v1' = (m1 * v1 + m2 * v2 - m2 * v2') / m1,代入(2),得
1/2 * m1 * v1^2 + 1/2 * m2 * v2^2 = 1/2 * m1 * (m1 * v1 + m2 * v2 - m2 * v2')^2 + 1/2 * m2 * v2'^2
=> v2' = (2 * m2 * v1 + v2 * (m1 - m2)) / (m1 + m2),则
=> v1' = (2 * m1 * v2 + v1 * (m1 - m2)) / (m1 + m2)

我们现在得到的公式可以用于处理当两球球心移动方向共线(1-D处理)时的速度关系,或者不共线(2-D处理)时共线方向的速度分量的关系。不管是前者还是后者,我们都需要把它们的速度分解到同一个轴上才能应用上述公式进行处理。

二、深入分析

首先我要说明一件事情:当两球碰撞时,它们的速度可以分解为球心连线方向的分速度和碰撞点切线方向的分速度。而由于它们之间相互作用的力只是在切点上,也就是球心连线方向上,因此我们只用处理这个方向上的力。而在切线方向上,它们不存在相互作用的力,而且在理想环境下也没有外力,因此这个方向上的力在碰撞前后都不变,因此不处理。好,知道了这件事情之后,我们就知道该如何把两球的速度分解到同一个轴上进行处理。

现在看上面的分析图,s和t是我们根据两个相碰球m1和m2的位置建立的辅助轴,我们一会就将把速度投影到它们上面。v1v2分别是m1和m2的初速度,v1'v2'是它们碰撞后的末速度,也就是我们要求的。s'是两球球心的位置向量,t'是它的逆时针正交向量。s1s'的单位向量,t1t'的单位向量。
我们的思路是这样的:首先我们假设两球已经相碰(在程序中可以通过计算两球球心之间的距离来判断)。接下来我们计算一下s't',注意s't'的方向正反无所谓(一会将解释),现在设m1球心为(m1x, m1y),m2球心为(m2x, m2y),则s'为(m1x-m2x, m1y-m2y),t'为(m2y-m1y, m1x-m2x)(第一篇的知识)。
则设
sM = sqrt((m1x-m2x)^2+(m1y-m2y)^2),
tM = sqrt((m2y-m1y)^2+(m1x-m2x)^2),有
s1
= ((m1x-m2x)/sM, (m1y-m2y)/sM) = (s1x, s1y)
t1 = ((m2y-m1y)/tM, (m1x-m2x)/tM) = (t1x, t1y)

现在s和t轴的单位向量已经求出了,我们根据向量点乘的几何意义,计算v1v2s1t1方向上的投影值,然后将s轴上投影值代
入公式来计算s方向碰撞后的速度。注意,根据刚才的说明,t方向的速度不计算,因为没有相互作用的力,因此,t方向的分速度不变。所以我们要做的就是:把v1投影到s和t方向上,再把v2投影到s和t方向上,用公式分别计算v1v2在s方向上的投影的末速度,然后把得到的末速度在和原来v1v2在t方向上的投影速度再合成,从而算出v1'v2'。好,我们接着这个思路做下去:

先算v1(v1x, v1y)在s和t轴的投影值,分别设为v1s和v1t:

v1s = v1.s1
=> v1s = v1x * s1x + v1y * s1y
v1t = v1.t1
=> v1t = v1x * t1x + v1y * t1y
再算v2(v2x, v2y)在s和t轴的投影值,分别设为v2s和v2t:

v2s = v2.s1
=> v2s = v2x * s1x + v2y * s1y
v2t = v2.t1
=> v2t = v2x * t1x + v2y * t1y
接下来用公式
v1' = (2 * m1 * v2 + v1 * (m1 - m2)) / (m1 + m2)
v2' = (2 * m2 * v1 + v2 * (m1 - m2)) / (m1 + m2)
计算v1s和v2s的末值v1s'和v2s',重申v1t和v2t不改变:
假设m1 = m2 = 1

v1s' = (2 * 1 * v2s + v1s * (1 - 1)) / (1 + 1)
v2s' = (2 * 1 * v1s + v2s * (1 - 1)) / (1 + 1)
=> v1s' = v2s
=> v2s' = v1s

好,下一步,将v1s'和v1t再合成得到v1',将v2s'和v2t再合成得到v2',我们用向量和来做:

首先求出v1t和v2t在t轴的向量v1t'v2t'(将数值变为向量)

v1t' = v1t * t1 = (v1t * t1x, v1t * t1y)
v2t' = v2t * t1 = (v2t * t1x, v2t * t1y)
再求出v1s'和v2s'在s轴的向量v1s'v2s'(将数值变为向量)
v1s'
= v1s' * s1 = (v1s' * s1x, v1s' * s1y)
v2s'
= v2s' * s1 = (v2s' * s2x, v2s' * s2y)

最后,合成,得

v1' = v1t' + v1s' = (v1t * t1x + v1s' * s1x, v1t * t1y + v1s' * s1y)
v2' = v2t' + v2s' = (v2t * t1x + v2s' * s2x, v2t * t1y + v2s' * s2y)

从而就求出了v1'v2'。下面解释为什么说s't'的方向正反无所谓:不论我们在计算s'时使用m1的球心坐标减去m2的球心坐标还是相反的相减顺序,由于两球的初速度的向量必有一个和s1是夹角大于90度小于270度的,而另外一个与s1的夹角在0度和90度之间或者说在270度到360度之间,则根据向量点积的定义|a|*|b|*cosA,计算的到的两个投影值一个为负另一个为正,也就是说,速度方向相反,这样就可以用上面的公式区求得末速度了。同时,求出的末速度也是方向相反的,从而在转换为v1s'v2s'时也是正确的方向。同样的,求t'既可以是用s'逆时针90度得到也可以是顺时针90度得到。

三、编写代码

按照惯例,该编写代码了,其实编写的代码和上面的推导过程极为相似。但为了完整,我还是打算写出来。

// 用于球体碰撞响应的函数,其中v1a和v2a为两球的初速度向量,
// v1f和v2f是两球的末速度向量。
// m1和m2是两球的位置向量
// s'的分量为(sx, sy),t'的分量为(tx, ty)
// s1是s的单位向量,分量为(s1x, s1y)
// t1是t的单位向量,分量为(t1x, t1y)

void Ball_Collision(v1a, v2a, &v1f, &v2f, m1, m2){

// 求出s'
double sx = m1.x - m2.x ;
double sy = m1.y - m2.y ;
// 求出s1
double s1x = sx / sqrt(sx*sx + sy*sy) ;
double s1y = sy / sqrt(sx*sx + sy*sy) ;
// 求出t'
double tx = -sy ;
double ty = sx ;
// 求出t1
double t1x = tx / sqrt(tx*tx + ty*ty) ;
double t1y = ty / sqrt(tx*tx + ty*ty) ;
// 求v1a在s1上的投影v1s
double v1s = v1a.x * s1x + v1a.y * s1y ;
// 求v1a在t1上的投影v1t
double v1t = v1a.x * t1x + v1a.y * t1y ;
// 求v2a在s1上的投影v2s
double v2s = v2a.x * s1x + v2a.y * s1y ;
// 求v2a在t1上的投影v2t
double v2t = v2a.x * t1x + v2a.y * t1y ;
// 用公式求出v1sf和v2sf
double v1sf = v2s ;
double v2sf = v1s ;

// 最后一步,注意这里我们简化一下,直接将v1sf,v1t和v2sf,v2t投影到x,y轴上,也就是v1'和v2'在x,y轴上的分量
// 先将v1sf和v1t转化为向量
double nsx = v1sf * s1x ;
double nsy = v1sf * s1y ;
double ntx = v1t * t1x ;
double nty = v1t * t1y ;
// 投影到x轴和y轴
// x轴单位向量为(1,0),y轴为(0,1)
// v1f.x = 1.0 * (nsx * 1.0 + nsy * 0.0) ;
// v1f.y = 1.0 * (nsx * 0.0 + nsy * 1.0) ;
// v1f.x+= 1.0 * (ntx * 1.0 + nty * 0.0) ;
// v1f.y+= 1.0 * (ntx * 0.0 + nty * 1.0) ;

v1f.x = nsx + ntx ;
v1f.y = nsy + nty ;

// 然后将v2sf和v2t转化为向量
nsx = v2sf * s1x ;
nsy = v2sf * s1y ;
ntx = v2t * t1x ;
nty = v2t * t1y ;
// 投影到x轴和y轴
// x轴单位向量为(1,0),y轴为(0,1)
// v2f.x = 1.0 * (nsx * 1.0 + nsy * 0.0) ;
// v2f.y = 1.0 * (nsx * 0.0 + nsy * 1.0) ;
// v2f.x+= 1.0 * (ntx * 1.0 + nty * 0.0) ;
// v2f.y+= 1.0 * (ntx * 0.0 + nty * 1.0) ;
v2f.x = nsx + ntx ;
v2f.y = nsy + nty ;

}// end of function
呼~~是不是感觉有点乱阿?不管怎么样,我有这种感觉。但我们确实完成了它。希望你能够理解这个计算的过程,你完全可以依照这个过程自己编写更高效的代码,让它看上去更清楚:)至此位置,我们已经掌握了编写一个台球游戏的基本知识了,Let's make it!

事实上,一切才刚刚起步,我们还有很多没有解决的问题,比如旋转问题,击球的角度问题等等,你还会深入的研究一下,对吗?一旦你有了目标,坚持下去,保持激情,总会有成功的一天:)这次就到这里,下次我们接着研究,Bye for now~~

<5>物体的旋转
欢迎回来这里!此次我们要讨论向量的旋转问题,包括平面绕点旋转和空间绕轴旋转两部分。对于游戏程序员来说,有了向量的旋转,就代表有了操纵游戏中物体旋转的钥匙,而不论它是一个平面精灵还是一组空间的网格体亦或是我们放在3-D世界某一点的相机。我们仍需借助向量来完成我们此次的旅程,但这还不够,我们还需要一个朋友,就是矩阵,一个我们用来对向量进行线性变换的GooL GuY。就像我们刚刚提及向量时所做的一样,我们来复习一下即将用到的数学知识。(这部分知识我只会一带而过,因为我将把重点放在后面对旋转问题的分析上)

一、矩阵的基本运算及其性质
对于3x3矩阵(也叫3x3方阵,行列数相等的矩阵也叫方阵)m和M,有
1、矩阵加减法
m +(-) M =
[a b c]      [A B C]   [a+(-)A b+(-)B c+(-)C]
[d e f] +(-) [D E F] = [d+(-)D e+(-)E f+(-)F]
[g h i]      [G H I]   [g+(-)G h+(-)H i+(-)I]
性质:
1)结合律 m + (M + N) = (m + M)  + N
2) 交换律 m + M = M + m
2、数量乘矩阵
k x M =
    [A B C]   [kxA kxB kxC]
k x [D E F] = [kxD kxE kxF]
    [G H I]   [kxG kxH kxI]
性质:
k和l为常数
1) (k + l) x M = k x M + l x M
2) k x (m + M) = k x m + k x M
3) k x (l x M) = (k x l) x M
4) 1 x M = M
5) k x (m x M) = (k x m) x M = m x (k x M)
3、矩阵乘法
m x M =
[a b c]   [A B C}   [axA+bxD+cxG axB+bxE+cxH axC+bxF+cxI]
[d e f] x [D E F] = [dxA+exD+fxG dxB+exE+fxH dxC+exF+fxI]
[g h i]   [G H I]   [gxA+hxD+ixG gxB+hxE+ixH gxC+hxF+ixI]
可以看出,矩阵相乘可以进行的条件是第一个矩阵的列数等于第二个矩阵的行数。
由矩阵乘法的定义看出,矩阵乘法不满足交换率,即在一般情况下,m x M != M x m。
性质:
1) 结合律 (m x M) x N = m x (M x N)
2) 乘法加法分配律 m x (M + N) = m x M + m x N ; (m + M) x N = m x N + M x N
4、矩阵的转置

m' =
[a b c]'    [a d g]
[d e f]  = [b e h] 
[g h i]     [c f i ]

性质:
1)(m x M)' = M' x m'
2)(m')' = m
3)(m + M)' = m' + M'
4)(k x M)' = k x M'   
5、单位矩阵
      [1 0 0]
E = [0 1 0] 称为3级单位阵
      [0 0 1]
性质:对于任意3级矩阵M,有E x M = M ; M x E = M

6、矩阵的逆
如果3x3级方阵m,有m x M = M x m = E,这里E是3级单位阵,则可以说m是可逆的,它的逆矩阵为M,也记为m^-1。相反的,也可以说M是可逆的,逆矩阵为m,也记为M^-1。
性质:
1) (m^-1)^-1 = m
2) (k x m)^-1 = 1/k x m^-1
3)(m')^-1 = (m^-1)'
4) (m x M)^-1 = M^-1 x n^-1
矩阵求逆有几种算法,这里不深入研究,当我们用到的时候在讨论。
在我们建立了矩阵的概念之后,就可以用它来做坐标的线性变换。好,现在我们开始来使用它。

二、基础的2-D绕原点旋转

首先是简单的2-D向量的旋转,以它为基础,我们会深入到复杂的3-D旋转,最后使我们可以在3-D中无所不能的任意旋转。

在2-D的迪卡尔坐标系中,一个位置向量的旋转公式可以由三角函数的几何意义推出。比如上图所示是位置向量R逆时针旋转角度B前后的情况。在左图中,我们有关系:
x0 = |R| * cosA
y0 = |R| * sinA
=>
cosA = x0 / |R|
sinA = y0 / |R|
在右图中,我们有关系:
x1 = |R| * cos(A+B)
y1 = |R| * sin(A+B)
其中(x1, y1)就是(x0, y0)旋转角B后得到的点,也就是位置向量R最后指向的点。我们展开cos(A+B)和sin(A+B),得到
x1 = |R| * (cosAcosB - sinAsinB)
y1 = |R| * (sinAcosB + cosAsinB)
现在把
cosA = x0 / |R|
sinA = y0 / |R|
代入上面的式子,得到
x1 = |R| * (x0 * cosB / |R| - y0 * sinB / |R|)
y1 = |R| * (y0 * cosB / |R| + x0 * sinB / |R|)
=>
x1 = x0 * cosB - y0 * sinB
y1 = x0 * sinB + y0 * cosB
这样我们就得到了2-D迪卡尔坐标下向量围绕圆点的逆时针旋转公式。顺时针旋转就把角度变为负:
x1 = x0 * cos(-B) - y0 * sin(-B)
y1 = x0 * sin(-B) + y0 * cos(-B)
=>
x1 = x0 * cosB + y0 * sinB
y1 = -x0 * sinB + y0 * cosB

现在我要把这个旋转公式写成矩阵的形式,有一个概念我简单提一下,平面或空间里的每个线性变换(这里就是旋转变换)都对应一个矩阵,叫做变换矩阵。对一个点实施线性变换就是通过乘上该线性变换的矩阵完成的。好了,打住,不然就跑题了。

所以2-D旋转变换矩阵就是:
[cosA  sinA]      [cosA -sinA]
[-sinA cosA] 或者 [sinA cosA]
我们对点进行旋转变换可以通过矩阵完成,比如我要点(x, y)绕原点逆时针旋转:
          [cosA  sinA]  
[x, y] x  [-sinA cosA] = [x*cosA-y*sinA  x*sinA+y*cosA]
为了编程方便,我们把它写成两个方阵
[x, y]   [cosA  sinA]   [x*cosA-y*sinA  x*sinA+y*cosA]
[0, 0] x [-sinA cosA] = [0              0            ]
也可以写成
[cosA -sinA]   [x 0]   [x*cosA-y*sinA  0]
[sinA  cosA] x [y 0] = [x*sinA+y*cosA  0]

三、2-D的绕任一点旋转

下面我们深入一些,思考另一种情况:求一个点围绕任一个非原点的中心点旋转。
我们刚刚导出的公式是围绕原点旋转的公式,所以我们要想继续使用它,就要把想要围绕的那个非原点的中心点移动到原点上来。按照这个思路,我们先将该中心点通过一个位移向量移动到原点,而围绕点要保持与中心点相对位置不变,也相应的按照这个位移向量位移,此时由于中心点已经移动到了圆点,就可以让同样位移后的围绕点使用上面的公式来计算旋转后的位置了,计算完后,再让计算出的点按刚才的位移向量 逆 位移,就得到围绕点绕中心点旋转一定角度后的新位置了。看下面的图

现在求左下方的蓝色点围绕红色点旋转一定角度后的新位置。由于红色点不在原点,所以可以通过红色向量把它移动到原点,此时蓝色的点也按照这个向量移动,可见,红色和蓝色点的相对位置没有变。现在红色点在原点,蓝色点可以用上面旋转变换矩阵进行旋转,旋转后的点在通过红色向量的的逆向量回到它实际围绕下方红色点旋转后的位置。

在这个过程中,我们对围绕点进行了三次线性变换:位移变换-旋转变换-位移变换,我们把它写成矩阵形式:
设红色向量为(rtx, rty)
[x y 1]   [1   0   0]    [cosA  sinA 0]    [1    0     0]    [x' y' -]
[0 1 0] x [0   1   0] x [-sinA cosA 0] x [0    1     0] = [-  -  -]
[0 0 1]   [rtx rty 1]    [0       0     1]    [-rtx -rty 1]    [-  -  -]

最后得到的矩阵的x'和y'就是我们旋转后的点坐标。
注意到矩阵乘法满足结合律:(m x M) x N = m x (M x N),我们可以先将所有的变换矩阵乘在一起,即
      [1   0   0]    [cosA  sinA 0]   [1    0    0] 
M = [0   1   0] x [-sinA cosA 0] x [0    1    0]  
      [rtx rty 1]    [0       0     1]   [-rtx -rty 1]  

然后再让
[x y 1]
[0 1 0] x M
[0 0 1]
像这样归并变换矩阵是矩阵运算一个常用的方法,因为当把诸多变换矩阵归并为一个矩阵之后,对某点或向量的重复变换只需要乘一个矩阵就可以完成,减少了计算的开销。
本小节讨论的这种“其他变换-绕点旋转变换-其他变换”的思想很重要,因为有时候复杂一些的旋转变换不可能一步完成,必须使用这种旁敲侧击、化繁为简的方法,尤其是在3-D空间中,可能需要在真正做规定度数的旋转前还要做一些其他必要旋转变换,也就是要做很多次的旋转,但总体的思想还是为了把复杂的问题分成若干简单的问题去解决,而每一个简单问题都需要一个变换矩阵来完成,所以希望读者深入思考一下这种方法。
好,2-D的旋转探讨完毕。接下来,我们进入3-D空间,讨论更为复杂一些的旋转。Here We Go!

四、基础的3-D绕坐标轴方向旋转

就像2-D绕原点旋转一样,3-D的绕坐标轴旋转是3-D旋转的基础,因为其他复杂的3-D旋转最后都会化简为绕坐标轴旋转。其实,刚才我们推导出的在xoy坐标面绕o旋转的公式可以很容易的推广到3-D空间中,因为在3-D直角坐标系中,三个坐标轴两两正交,所以z轴垂直于xoy面,这样,在xoy面绕o点旋转实际上在3-D空间中就是围绕z轴旋转,如下图左所示:

这张图描述了左手系中某点在xoy、yoz、xoz面上围绕原点旋转的情况,同时也是分别围绕z、x、y坐标轴旋转。可见在3-D空间中绕坐标轴旋转相当于在相应的2-D平面中围绕原点旋转。我们用矩阵来说明:

p(x, y, z)是3-D空间中的一点,也可以说是一个位置向量,当以上图中的坐标为准,p点所围绕的中心轴指向你的屏幕之外时,有
p
绕z轴逆时针和顺时针旋转角度A分别写成:
[x y z 1]    [cosA -sinA 0 0]    [x y z 1]   [cosA sinA  0 0]
[0 1 0 0] x [sinA cosA  0 0] 和 [0 1 0 0] x [-sinA cosA 0 0]
[0 0 1 0]   [0    0     1 0]    [0 0 1 0]   [0     0    1 0]
[0 0 0 1]   [0    0     0 1]    [0 0 0 1]   [0     0    0 1]
p绕x轴逆时针和顺时针旋转角度A分别写成:
[x y z 1]   [1 0     0    0]    [x y z 1]   [1 0     0    0]
[0 1 0 0] x [0 cos  -sinA 0] 和 [0 1 0 0] x [0 cosA  sinA 0]
[0 0 1 0]   [0 sin  cosA  0]    [0 0 1 0]   [0 -sinA cosA 0]
[0 0 0 1]   [0 0    0     1]    [0 0 0 1]   [0 0     0    1]
p绕y轴逆时针和顺时针旋转角度A分别写成:
[x y z 1]    [cosA  0 sinA 0]     [x y z 1]    [cosA 0  -sinA 0]
[0 1 0 0] x [0     1    0   0]  和 [0 1 0 0] x [0     1  0    0]
[0 0 1 0]   [-sinA 0 cosA 0]     [0 0 1 0]    [sinA  0  cosA 0]
[0 0 0 1]   [0      0   0    1]     [0 0 0 1]    [0     0  0    1]
以后我们会把它们写成这样的标准4x4方阵形式,Why?为了便于做平移变换,还记得上小节做平移时我们把2x2方阵写为3x3方阵吗?
让我们继续研究。我们再把结论推广一点,让它适用于所有和坐标轴平行的轴,具体一点,让它适用于所有和y轴平行的轴。
这个我们很快可以想到,可以按照2-D的方法“平移变换-旋转变换-平移变换”来做到,看下图

要实现point绕axis旋转,我们把axis按照一个位移向量移动到和y轴重合的位置,也就是变换为axis',为了保持point和axis的相对位置不变,point也通过相同的位移向量做相应的位移。好,现在移动后的point就可以用上面的旋转矩阵围绕axis'也就是y轴旋转了,旋转后用相反的位移向量位移到实际围绕axis相应度数的位置。我们还是用矩阵来说明:
假设axis为x = s, z = t,要point(x, y, z)围绕它逆时针旋转度数A,按照“平移变换-旋转变换-位移变换”,我们有

[x y z 1]   [1  0 0  0]   [cosA  0 sinA 0]   [1 0 0 0]   [x' y z' -]
[0 1 0 0]   [0  1 0  0]   [0     1 0    0]   [0 1 0 0]   [-  - -  -]
[0 0 1 0] x [0  0 1  0] x [-sinA 0 cosA 0] x [0 0 1 0] = [-  - -  -]
[0 0 0 1]   [-s 0 -t 1]   [0     0 0    1]   [s 0 t 1]   [-  - -  -]

则得到的(x', y, z')就是point围绕axis旋转角A后的位置。
同理,平行于x轴且围绕轴y=s,z=t逆时针旋转角A的变换为

[x y z 1]   [1  0 0  0]    [1    0    0     0]   [1 0 0 0]   [x  y' z' -]
[0 1 0 0]   [0  1 0  0]    [0 cosA -sinA 0]   [0 1 0 0]   [-  -  -  -]
[0 0 1 0] x [0  0 1  0] x [0 sinA cosA  0] x [0 0 1 0] = [-  -  -  -]
[0 0 0 1]   [0 -s -t 1]    [0    0    0     1]   [0 s t 1]   [-  -  -  -]

平行于z轴且围绕轴x=s,y=t逆时针旋转角A的变换为

[x y z 1]    [1  0  0  0]   [cosA -sinA 0 0]   [1 0 0 0]    [x' y' z  -]
[0 1 0 0]   [0  1  0  0]   [sinA cosA  0 0]   [0  1 0 0]   [-  -  -  -]
[0 0 1 0] x [0  0  1  0] x [0    0     1   0] x [0 0 1 0] = [-  -  -  -]
[0 0 0 1]    [-s -t 0  1]   [0    0     0   1]   [s  t 0 1]    [-  -  -  -]

逆时针旋转就把上面推出的相应逆时针旋转变换矩阵带入即可。至此我们已经讨论了3-D空间基本旋转的全部,接下来的一小节是我们3-D旋转部分的重头戏,也是3-D中功能最强大的旋转变换。

五、3-D绕任意轴的旋转

Wow!终于来到了最后一部分,这一节我们将综合运用上面涉及到的所有旋转知识,完成空间一点或着说位置向量围绕空间任意方向旋转轴的旋转变换(我在下面介绍的一种方法是一个稍微繁琐一点的方法,大体上看是利用几个基本旋转的综合。我将在下一篇中介绍一个高档一些的方法)。

何谓任意方向的旋转轴呢?其实就是空间一条直线。在空间解析几何中,决定空间直线位置的两个值是直线上一点以及直线的方向向量。在旋转中,我们把这个直线称为一个旋转轴,因此,直线的这个方向向量我们叫它轴向量,它类似于3-D动画中四元数的轴向量。我们在实际旋转之前的变换矩阵需要通过把这个轴向量移动到原点来获得。
我们先讨论旋转轴通过原点的情况。目前为止对于3-D空间中的旋转,我们可以做的只是绕坐标轴方向的旋转。因此,当我们考虑非坐标轴方向旋转的时候,很自然的想到,可以将这个旋转轴通过变换与某一个坐标轴重合,同时,为了保持旋转点和这个旋转轴相对位置不变,旋转点也做相应的变换,然后,让旋转点围绕相应旋转轴重合的坐标轴旋转,最后将旋转后的点以及旋转轴逆变换回原来的位置,此时就完成了一点围绕这个非坐标轴方向旋转轴的旋转。我们再来看图分析。

图中有一个红色的分量为(x0, y0, z0)的轴向量,此外有一个蓝色位置向量围绕它旋转,由于这个轴向量没有与任何一个坐标轴平行,我们没有办法使用上面推导出的旋转变换矩阵,因此必须将该轴变换到一个坐标轴上,这里我们选择了z轴。在变换红色轴的同时,为了保持蓝色位置向量同该轴的相对位置不变,也做相应的变换,然后就出现中图描述的情况。接着我们就用可以用变换矩阵来围绕z轴旋转蓝色向量相应的度数。旋转完毕后,再用刚才变换的逆变换把两个向量相对位置不变地还原到初始位置,此时就完成了一个点围绕任意过原点的轴的旋转,对于不过原点的轴我们仍然用“位移变换-旋转变换-位移变换”的方法,一会讨论。

在理解了基本思路之后,我们来研究一下变换吧!我们就按上图将红色轴变到z轴上,开始吧!
首先我们假设红轴向量是一个单位向量,因为这样在一会求sin和cos时可以简化计算,在实际编程时可以先将轴向量标准化。然后我准备分两步把红色轴变换到z轴上去:
1)将红色轴变换到yoz平面上
2) 将yoz平面上的红色轴变到z轴上
至于这两个变换的方法...我实在没有别的办法了,只能够旋转了,你觉得呢?先把它旋转到yoz平面上。
我们设轴向量旋转到yoz面的变换为(绕z轴旋转):
[cosA  sinA   0   0]
[-sinA cosA   0   0]
[0       0      1   0]
[0       0      0   1] 

接着我们要求出cosA和sinA,由上图,沿着z轴方向看去,我们看到旋转轴向量到yoz面在xoy面就是将轴的投影向量旋转角度A到y轴上,现在我不知道角度A,但是我们可以利用它直接求出cosA和sinA,因为我们知道关系:
cosA = y0 / 轴向量在xoy面的投影长
sinA = x0 / 轴向量在xoy面的投影长
我们设轴向量的投影长为lr = sqrt(x0^2 + y0^2),呵呵,现在,我们第一步的变换矩阵就出来了:
[y0/lr  x0/lr 0 0]
[-x0/lr y0/lr 0 0]
[0      0     1  0]
[0      0     0  1]
同时我们得到逆变换矩阵:
[y0/lr -x0/lr 0 0]
[x0/lr y0/lr  0 0]
[0      0     1 0]
[0      0     0 1]

然后我们进行第二步:将yoz平面上的红色轴变到z轴上。我们的变换矩阵是(绕x轴旋转):

[1 0     0    0]
[0 cosB  sinB 0]
[0 -sinB cosB 0]
[0 0     0    1]

由图,这是经第一次旋转后的轴向量在yoz面中的情形,此次我们要求出上面变换中的cosB和sinB,我们仍不知道角度B,但我们还是可以利用它求cosB和sinB。由于第一次旋转是围绕z轴,所以轴向量的z分量没有变,还是z0。此外,轴向量现在的y分量和原来不同了,我们再看一下第一次变换那张图,可以发现轴向量在旋转到yoz面后,y分量变成了刚才轴向量在xoy面上的投影长lr了。Yes!我想是时候写出cosB和sinB了:
cosB = z0 / 轴向量的长
sinB = lr / 轴向量的长
还记得我们刚才假设轴向量是一个单位向量吗?所以
cosB = z0
sinB = lr
至此我们的第二个变换就出来了:
[1 0   0   0]
[0 z0  lr  0]
[0 -lr z0  0]
[0 0   0   1]
相应逆变换矩阵:
[1 0   0   0]
[0 z0  -lr 0]
[0 lr  z0  0]
[0 0   0   1]
现在总结一下,我们对于空间任意点围绕某个任意方向且过原点的轴旋转的变换矩阵就是:
      [y0/lr  x0/lr 0 0]   [1 0   0  0]   [cosA  sinA 0 0]   [1 0  0   0]   [y0/lr  -x0/lr 0 0]
      [-x0/lr y0/lr 0 0]   [0 z0  lr 0]   [-sinA cosA 0 0]   [0 z0 -lr 0]   [x0/lr  y0/lr  0 0]
M = [0      0     1 0] x [0 -lr z0 0] x [0     0    1 0] x [0 lr z0  0] x [0      0      1 0]
      [0      0     0 1]   [0 0   0  1]   [0     0    0 1]   [0 0  0   1]   [0      0      0 1]

上面的变换是“旋转变换-旋转变换-旋转变换-旋转变换-旋转变换”的变换组。当我们需要让空间中的某个位置向量围绕一个轴旋转角度A的时候,就可以用这个向量相应的矩阵乘上这个M,比如
[x y 0 0]         [x' y' z' -]
[0 1 0 0]         [-  -  -  -]
[0 0 1 0] x M = [-  -  -  -]
[0 0 0 1]         [-  -  -  -]
当然,M中矩阵相应的元素是根据轴向量得到的。
以上的变换矩阵是通过把轴向量变到z轴上得到的,而且是先旋转到yoz面上,然后再旋转到z轴上。我们也可以不这样做,而是先把轴向量旋转到xoz面上,然后再旋转到z轴上。此外,我们还可以把轴向量变到x或y轴上,这一点我们可以自己决定。虽然变换不同,但推导的道理是相同的,都是这种“其他变换-实际旋转变换-其他变换”的渗透形式。

刚才分析的是旋转轴过原点的情况,对于一般的旋转轴,虽然我们也都是把它的轴向量放到原点来考虑,但我们不能只是让旋转点围绕过原点的轴向量旋转完就算完事,我们仍需要采用“平移变换-旋转变换-平移变换”方法。即先将旋转轴平移到过原点方向,旋转点也做相应平移,接着按上面推出的变换阵旋转,最后将旋转轴和点逆平移回去。这里,我们只需在M的左右两边各加上一个平移变换即可。这个平移变换的元素是根据轴向量与原点之间的距离向量得到的,比如旋转轴与原点的距离向量是(lx, ly, lz),则我们的变换就变成
      [1   0    0  0]       [1  0  0  0]
      [0   1    0  0]       [0  1  0  0]
m = [0   0    1  0] x M x [0  0  1  0]
      [-lx -ly -lz 1]       [lx ly lz 1]

变换矩阵m就是全部7个变换矩阵的归并,适用于各种旋转情况。

我们现在已经讨论完了一般的2-D、3-D旋转了。可以看出其基本的思想还是能够化繁为简的变换、归并。而实际的旋转也仍是用我们最最基本的2-D绕原点旋转公式。其实还有很多的旋转效果可以用我们上面的变换、公式稍加修改获得。比如螺旋形旋转、旋转加前进、随机旋转等等。下一篇将介绍一个用的最多的高档一些的方法,下次见。

<6>3-D空间中的基变换与坐标变换

一、空间坐标系的基和基矩阵
在3-D空间中,我们用空间坐标系来规范物体的位置,空间坐标系由3个相互垂直的坐标轴组成,我们就把它们作为我们观察3-D空间的基础,空间中物体的位置可以通过它们来衡量。当我们把这3个坐标轴上单位长度的向量记为3个相互正交的单位向量i,j,k,空间中每一个点的位置都可以被这3个向量线性表出,如P<1,-2,3>这个点可以表为i-2j+3k。

我们把这3个正交的单位向量称为空间坐标系的,它们单位长度为1且正交,所以可以成为标准正交基。三个向量叫做基向量。现在我们用矩阵形式写出基向量和基。
i =  | 1 0 0 |
j =  | 0 1 0 |
k =  | 0 0 1 |
      | i |    | 1 0 0 |   
B = | j | =  | 0 1 0 |
      | k |    | 0 0 1 |

这样的矩阵我们叫它基矩阵。有了基矩阵,我们就可以把空间坐标系中的一个向量写成坐标乘上基矩阵的形式,比如上面的向量P可以写成:

P = C x B
=>
| 1 -2 3 | =
| 1 -2 3 | x
| 1 0 0 |                         
| 0 1 0 |
| 0 0 1 |

这样的话,空间坐标系下的同一个向量在不同的基下的坐标是不同的。

二、局部坐标系和局部坐标

和空间坐标系(也可以叫做全局坐标系或者世界坐标系)并存的称为局部坐标系(也叫坐标架——coordinate frame),它有自己的基,这些基向量把空间坐标系作为参考系。比如
      | x'|   | -1  0   0  |
B' = | y'| = | 0   1   0  |
      | z'|   | 0   0   -1 |
       | x''|   | 2^½ /2    0   2^½ /2    |

B'' = | y''| = | 0        -1   0          |

       | z''|   | -(2^½) /2   0   2^½ /2  |
就是两个局部坐标系的基,如图:

现在我们可以把上面那个空间坐标中的向量P|1 -2 3|(以后都用矩阵表示)表示在不同的基下,我把它写成一个大长串的式子:
                      | x' |                        | x''|
P = | Px' Py' Pz' | x | y' | = | Px'' Py'' Pz'' | x | y''|

                      | z' |                        | z''|

这里| Px' Py' Pz'|是P在B'下的坐标,| Px'' Py'' Pz''|是P在B''下的坐标,我把它写的具体点吧:

| 1 -2 3 | = | -1 -2 -3 | x
| -1 0  0 |

| 0  1  0 |

| 0  0 -1 |

= | 2*2^½   -2   2^½ | x
| 2^½ /2       0     2^½ /2|

|     0           -1       0      |

| -(2^½) /2    0    2^½ /2|

这就是说,在空间坐标系下面的向量| 1 -2 3 |在基B'下的坐标为|-1 -2 -3|,在B''下的坐标为| 2*2^½   -2   2^½ |。当然空间坐标系也有自己的基B|i j k|^T(因为是列向量,所以写成行向量的转置),但我们现在是拿它当作一个参考系。

在研究了局部坐标系之后,我现在要分析两个应用它们的例子,先来看

三、空间坐标系中一个点围绕任一轴的旋转

上一篇讨论3-D空间旋转的时候说到有一个高档的方法做3-D空间任意轴旋转,现在我们的知识储备已经足够理解这个方法了(Quake引擎使用的就是这个方法)。

如上所示,空间坐标系中的一个局部坐标系xyz中有一个向量a(2,5,3)和一个点p(8,4,2)现在我要让p点围绕a向量旋转60度,得到p’点,该如何做呢?从目前掌握的旋转知识来看,我们有两个理论基础:

1)在一个坐标系中的一个点,如果要它围绕该坐标系中一个坐标轴旋转,就给它的坐标值乘相应的旋转矩阵,如

[cosA -sinA 0 ]
[sinA cosA  0 ]
[0    0     1 ]

等等。

2)我们已经学习了局部坐标系的理论了,知道空间中一个点在不同的坐标系中的坐标不同。利用这一点,我们可以很方便的让一个点或者向量在不同的坐标系之间转换。

我们联系这两个理论根据,得出我们的思路:

1构造另一个局部坐标系abc,使得a成为该坐标系的一个坐标轴。

2 把p的坐标变换到abc中,得到p’,用旋转公式让p’围绕已经成为坐标轴的a旋转,得到p’’。

3把p’’再变换回坐标系xyz,得到p’’’,则p’’’就是p围绕a旋转后的点。

下面我们逐步说明。

首先我们构造abc,我们有无数种方法构造,因为只要保证b、c之间以及他们和a之间都正交就可以了,但我们只要一个。根据上图,我们首先产生一个和a正交的b。这可以通过向量的叉乘来完成:我们取另一个向量v(显然,这个向量是不能和a共线的任何非零向量),让它和a决定一个平面x,然后让v叉乘a得到一个垂直于x的向量b,因为b垂直于x,而a在平面x上,因此b一定垂直于a,然后用a叉乘b得到c,最后单位化a、b、c,这样就得到了局部坐标系abc。

然后我们把p点变换到abc坐标系中,得到p’,即p’就是p在abc中的坐标:

|a b c| * p’= |x y z| * p

p’ = |a b c|^-1 * |x y z| * p

|ax bx cx| |1 0 0| |px|

p’ = |ay by cy| ^-1 * |0 1 0| * |py|

|az bz cz| |0 0 1| |pz|

注意这里|a b c|^-1即矩阵|a b c|的逆矩阵,因为a、b、c是三个正交向量,并且是单位向量,因此|a b c|是一个正交矩阵,正交矩阵的转置和逆相等,这是它的一个特性,因此上面的公式就可以写成:

|ax ay az| |1 0 0| |px|

p’ = |bx by bz| * |0 1 0| * |py|

|cx cy cz| |0 0 1| |pz|

这个时候p’就是p在abc坐标系下的坐标了。此时a已经是一个坐标轴了,我们可以用旋转矩阵来做。

p’’ = RotMatrix * p’

[1 0    0] |p’x|
p’’ = [0 cos60 -sin60] * |p’y|
[0 sin60 cos60] |p’z|

最后,我们把p’’再次变换回xyz坐标系,得到最终的p’’’

|a b c| * p’’ = |x y z| * p’’’

p’’’ = |x y z|^-1 * |a b c| * p’’

p’’’ = |a b c| * p’’

最后

p’’’ = |a b c| * RotMatrix * |a b c|^T * p = M * p

这样就得到了xyz坐标系中点p围绕a旋转60度后的点。

最后,我用Quake3引擎的相应函数(来自idSoftware ——quake3-1[1].32b-source——mathlib.c)来完成对这个算法的说明:

/*

===============

RotatePointAroundVector

dst是一个float[3],也就是p’’’

dir相当于a,point就是p,degrees是旋转度数

===============

*/

void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point,

float degrees ) {

float m[3][3];

float im[3][3];

float zrot[3][3];

float tmpmat[3][3];

float rot[3][3];

int i;

vec3_t vr, vup, vf;

float rad;

vf[0] = dir[0];

vf[1] = dir[1];

vf[2] = dir[2];

// 首先通过dir得到一个和它垂直的vr

// PerpendicularVector()函数用于构造和dir垂直的向量

// 也就是我们上面的第1步

PerpendicularVector( vr, dir );

// 通过cross multiply得到vup

// 现在已经构造出坐标轴向量vr, vup, vf

CrossProduct( vr, vf, vup );

// 把这三个单位向量放入矩阵中

m[0][0] = vr[0];

m[1][0] = vr[1];

m[2][0] = vr[2];

m[0][1] = vup[0];

m[1][1] = vup[1];

m[2][1] = vup[2];

m[0][2] = vf[0];

m[1][2] = vf[1];

m[2][2] = vf[2];

// 产生转置矩阵im

memcpy( im, m, sizeof( im ) );

im[0][1] = m[1][0];

im[0][2] = m[2][0];

im[1][0] = m[0][1];

im[1][2] = m[2][1];

im[2][0] = m[0][2];

im[2][1] = m[1][2];

// 构造旋转矩阵zrot

memset( zrot, 0, sizeof( zrot ) );

zrot[0][0] = zrot[1][1] = zrot[2][2] = 1.0F;

rad = DEG2RAD( degrees );

zrot[0][0] = cos( rad );

zrot[0][1] = sin( rad );

zrot[1][0] = -sin( rad );

zrot[1][1] = cos( rad );

// 开始构造变换矩阵M

// tmpmat = m * zrot

MatrixMultiply( m, zrot, tmpmat );

// rot = m * zrot * im

MatrixMultiply( tmpmat, im, rot );

// 则 rot = m * zrot * im 和我们上面推出的

// M = |a b c| * RotMatrix * |a b c|^T 一致

// 变换point这个点

// p’’’ = M * p

for ( i = 0; i < 3; i++ ) {

dst[i] = rot[i][0] * point[0] + rot[i][1] * point[1] + rot[i][2] * point[2];

}

}

四、世界空间到相机空间的变换

空间坐标系XYZ,相机坐标系UVN。这时候相机空间的基(以下简称相机)在空间坐标系中围绕各个坐标轴旋转了一定角度<a,b,c>,然后移动了<x,y,z>。对于模型我们可以看作相对于相机的逆运动,即模型旋转了一定角度<-a,-b,-c>,然后移动了<-x,-y,-z>,可以把相机和物体的运动看成两个互逆的变换。这样,可以通过对相机的变换矩阵求逆来得到模型的变换矩阵。下面来具体看一下,如何得到相机变换矩阵,并且求得它的逆矩阵。

首先声明一下,对于一个模型的变换,我们可以给模型矩阵左乘变换矩阵:

M x P = P'

| A B C D |

| E F G H |

| I J K L |

| M N O P |

x

| x |

| y |

| z |

| 1 |

=

| Ax + By + Cz + D |

| Ex + Fy + Gz + H |         
| Ix + Jy + Kz + L |

| Mx + Ny + Oz + P |

也可以右乘变换矩阵:

PT x MT = P'T

| x y z 1|   x

| A E I  M |

| B F J  N |   
| C G K O |

| D H L  P |

=  |Ax+By+Cz+D Ex+Fy+Gz+H Ix+Jy+Kz+L Mx+Ny+Oz+P|

可以看出两种变换方式是一个转置关系,结果只是形式上的不同,但这里我们使用后者,即右乘变换矩阵,因为比较普遍。

很显然,相机的变换可以分成两个阶段:旋转和平移。我们先来看旋转。

在空间坐标系中,相机旋转之前世界坐标系xyz和相机坐标系u0v0n0的各个轴向量的方向相同,有关系:

P = |Pu0 Pv0 Pn0| x
| u0 |

| v0 |

| n0 |

=  |Px Py Pz| x
| x |

| y |

| z |

这里P是空间坐标系中的一个向量。|u0 v0 n0|^T是相机基矩阵,|Pu0 Pv0 Pn0|是P在相机基矩阵下的坐标。|x y z|^T是
世界基矩阵,|Px Py Pz|是P在它下面的坐标。有Pu0 = Px, Pv0 =Py, Pn0 = Pz。

相机和向量P都旋转之后,有关系:

P' = |Pu0 Pv0 Pn0| x
| u |

| v |

| n |

= |Px' Py' Pz'| x
| x |

| y |

| z |

P'是P同相机一起旋转后的向量。|u v n|^T是相机旋转后的基矩阵,|Pu0 Pv0 Pn0|是P'在它下面的坐标,因为P是和相机一起旋转的,所以坐标不变。|x y z|^T仍为世界基矩阵,|Px' Py' Pz'|是P'在它下面的坐标。

现在看

因为|x y z|^T为一个单位阵,且Pu0 = Px, Pv0 =Py, Pn0 = Pz。 所以得到

|Pu0 Pv0 Pn0| x
| u |

| v |

| n |

= |Px' Py' Pz'| x
| x |

| y |

| z |

|Px Py Pz| x
| u |

| v |

| n |

= |Px' Py' Pz'| 

即|Px Py Pz|和相机一起旋转后变成|Px' Py' Pz'|,即P x R = P',而旋转变换矩阵R就是:

| u |

| v |

| n |

写成标准4x4矩阵:

| ux uy uz 0|

| vx vy vz 0|

| nx ny nz 0|

| 0  0  0  1|

平移矩阵T很简单:

| 1 0 0 0 |

| 0 1 0 0 |

| 0 0 1 0 |

| x y z 1 |

则相机矩阵就是:
             | ux uy uz 0 |     | 1 0 0 0 |
             | vx vy vz 0 |     | 0 1 0 0 |
C = R x T =                  x             
             | nx ny nz 0 |     | 0 0 1 0 |
             | 0  0  0  1 |     | x y z 1 |

它的逆矩阵,即相机的逆变换矩阵为

C-1 = T-1 x R-1 =
| 1  0  0  0 |

| 0  1  0  0 |

| 0  0  1  0 |

| -x -y -z 1 |

x  
| ux vx nx 0 |

| uy vy ny 0 |

| uz nz nz 0 |

| 0   0  0  1 |

=
| ux   vx   nx  0 |

| uy   vy   ny  0 |

| uz   vz   nz  0 |

|-T.u -T.v -T.n 1 |

矩阵的行列式

在任意方阵中都存在一个标量,称作该方阵的行列式。

 

线性运算法则

方阵M的行列式记作|M|或“det M”,非方阵矩阵的行列式是未定义的。n x n阶矩阵的行列式定义非常复杂,让我们先从2 x 2,3 x 3矩阵开始。

公式9.1给出了2 x 2阶矩阵行列式的定义:

注意,在书写行列式时,两边用竖线将数字块围起来,省略方括号。

下面的示意图能帮助记忆公式9.1,将主对角线和反对角线上的元素各自相乘,然后用主对角线元素的积减去反对角线元素的积。

3 x 3 阶矩阵的行列式定义如公式9.2所示:

可以用类似的示意图来帮助记忆。把矩阵M连写两遍,将主对角线上的元素和反对角线上的元素各自相乘,然后用各主对角线上元素积的和减去各反对角线上元素积的和。

如果将3 x 3阶矩阵的行解释为3个向量,那么矩阵的行列式等于这些向量的所谓“三元组积”。

假设矩阵M有r行c列,记法M{ij}表示从M中除去第i行和第j列后剩下的矩阵。显然,该矩阵有r-1行,c-1列,矩阵M{ij}称作M的余子式。

对方阵M,给定行、列元素的代数余子式等于相应余子式的有符号行列式,见公式9.3:

如上,用记法cij表示M的第i行,第j列元素的代数余子式。注意余子式是一个矩阵,而代数余子式是一个标量。代数余子式计算式中的项(–1)(i+j)有以棋盘形式使矩阵的代数余子式每隔一个为负的效果:

n维方阵的行列式存在着多个相等的定义,我们可以用代数余子式来定义矩阵的行列式(这种定义是递归的,因为代数余子式本身的定义就用到了矩阵的行列式)。

首先,从矩阵中任意选择一行或一列,对该行或列中的每个元素,都乘以对应的代数余子式。这些乘积的和就是矩阵的行列式。例如,任意选择一行,如行i,行列式的计算过程如公式9.4所示:

下面举一个例子,重写3x3矩阵的行列式:

综上,可导出4x4矩阵的行列式:

高阶行列式计算的复杂性是呈指数递增的。幸运的是,有一种称作”主元选择“的计算方法,它不影响行列式的值,但它能使特定的行或列中除了一个元素(主元)外其他元素全为0,这样仅一个代数余子式需要计算。

行列式的一些重要性质:

(1)矩阵积的行列式等于矩阵行列式的积:|AB| = |A||B|

        这可以扩展到多个矩阵:  |M1 M2 … Mn| = |M1| |M2| … |Mn-1| |Mn|

(2)矩阵转置的行列式等于原矩阵的行列式:|MT| = |M|

(3)如果矩阵的任意行或列全为0,那么它的行列式等于0.

(4)交换矩阵的任意两行或两列,行列式变负。

(5)任意行或列的非零积加到另一行或列上不会改变行列式的积。

 

几何解释

矩阵的行列式有着非常有趣的几何解释。2D中,行列式等于以基向量为两边的平行四边形的有符号面积(如图9.1所示)。有符号面积是指如果平行四边形相对于原来的方位”翻转“,那么面积变负。

3D中,行列式等于以变换后的基向量为三边的平行六面体的有符号的体积。3D中,如果变换使得平行六面体”有里向外“翻转,则行列式变负。

行列式与矩阵变换导致的尺寸改变相关,其中行列式的绝对值与面积(2D)、体积(3D)的改变相关,行列式的符号说明了变换矩阵是否包含镜像或投影。

矩阵的行列式还能对矩阵所代表的变换进行分类。如果矩阵的行列式为0,那么该矩阵包含投影。如果矩阵的行列式为负,那么该矩阵包含镜像。

什么是Pair Programming

Pair Programming是一个编程模式(Programming pattern)。两个程序员并排坐在一台电脑前,面对同一个显示器,使用同一个键盘,同一个鼠标一起工作。他们一起分析,一起设计,一起写测试例子,一起编码,一起单元测试,一起整合测试(Integration Test),一起写文档等。基本上所有的开发环节都一齐肩并肩地,平等地,互补地进行开发工作。

其它领域的“Pair Working”:

•      越野赛车

•      驾驶飞机

Pair Programming的角色(Role)

•Driver  The one who types

•Navigator The one who watches the back

•角色可以互换的

Pair Programming的疑问

疑问:

• 一个程序两个人写是不是一种浪费(可是两份工资,双倍资源哦)?

• 编程从来是一个人的活动。学校里这么教的,一直以来也是做么做的。

• 我不喜欢被人盯着工作,这样我不自在,无法工作。

• 这个笨家伙老是问问题,他/她不会看书么?我都无法专心工作了。

       ……

另一方面:

• Pair Programming被很多的大师级程序员推崇;

•不少大学都展开对Pair Programming的研究,并得到正面的结论;

• 很多尝试过的Developer都开始喜欢Pair Programming。

Pair Programming和Solo Programming的比较

一些研究数据:

1999年,University of Uath.两组学生,一组独自工作,一组Pair

Programming。(由助教预先设计和开发了Test Cases)

Pair Programming的历史

1995年,Larry Constantine在他的专栏中第一次提到了在他在P. J. Plaugherís software company, Whitesmiths, Ltd观察到一个现象:Collaborative Programming

·“两个程序员一起工作,可以比以往更快的交出完成并经过测试的代码,而且这些代码几乎是没有Bug的。”

•Collaborative Software Process(相对PSP)

•1996年,Kent Beck,Ward Cunningham 和Ron Jeffries一起提出了Extreme  Programming(XP),其中吸收了Collaborative Programming,并称为Pair Programming。

•Pair Programming是XP的一个key practice,也是XP成功的关键。

•随着XP在世界范围内被采用和练习,Pair Programming开始被接受。

为什么要Pair Programming

“The Human eye has an almost infinite capability for not seeing what it does not want to see…… Programmers, if left to their own devices, will ignore the most glaring errors in their output-errors that anyone else can see in an instant.”

    -- Gerald Weinberg

“Knowledge is commonly socially constructed through collaborative efforts toward shared objectives or by dialogues and challenges brought about by difference in persons’ perspective”

    --  Salomon

“三个臭皮匠,胜过一个诸葛亮”

    -- ?

企业管理层次:

• Pairs更有效的交流,相互学习和传递经验

• Pair Programming具备更高的效费比(cost-effective)

• Pair Programming能更好的处理人员流动

开发层次:

• Pairs能提供更好的设计质量和代码质量

• Pairs更强的问题解决能力

开发人员自身:

• Pairs一起工作能带来更多的信心

• Pairs一起工作能带来更高的满足感(程序员,用户和管理层)

不间断的Code Review

•Code Review的目的是不断的调整设计和编码质量的过程,也是为了及时发现问题和解决问题。避免把风险延后到QA阶段或Production阶段。

•开发中的Review主要包括:

     1) Design Review

     2) Code Review

     3) Test Review

     4) Document Review

传统开发过程的Review(例如印度的InfoSys公司)的问题:

1.   Peer Code Review,即程序员之间的互相Review

•缺乏Design Review

•不能持久,定时Code Review

•对需求和设计的不了解导致无法实现有效的Review

2.   Team Code Review

•什么时候开会做Review?不可能Team天天开会

•无法对所有的设计和Code进行Review

•面子问题

•效率低

Pair Programming提供不间断的Design review,Unit Test Review,Code Review,Document Review,避免了效果差的Team Code Review,也比抽查式的Peer Code Review有更好的质量。(CMM Level 3)

Pair Programming中,任何一段代码都至少被两双眼睛看过,两个脑袋思考过。结合Collective code ownership和小的Task (Small Engineering Task),代码被不断的Review。

•避免cow boy式的编程

•好代码的衡量标准:可读性和可维护性

•硬件设备价格的下降和速度的提升,使得代码效率不是考虑的重点(对大多数的商业应用)。对大部分的商业项目来说,更主要的顾虑是成本。而成本中人工占最大的比例。好的代码可以减少修改的成本。

•Pair Programming的互相督促可以提高代码的可读性。

Teamwork

Pair是一个最小单位的Team,而任何人都是工作在这样一个Team中。Developer的言行都会影响到其他的Developer( Partner),也受到其他Developer的影响。

Pair Programming避免了“我的Code”,使得代码的责任不属于某个人,而是属于一个Pair和整个Team,从而做到Collective Code Ownership,也避免个人英雄主义。

迫使程序员必须频繁的交流,增进知识经验的交流(Cross-Training)。

以人为本

•同伴的潜在压力( Peer Pressure )。Pair Programming的过程也是一个互相督促的过程。由于这种督促的压力,使得程序员更认真的工作。

•每个人每天的有效工作时段不超过3-4个小时。

•Pair Programming中Driver和Navigator的互换可以让程序员轮流工作,从而避免出现过度思考而导致观察力和判断力出现偏差。

•潜意识的有利竞争。当人在一个团队中工作,总是下意识的努力展现自己的优点。

•工作及时得到同伴的肯定,自信心和成就感(Self-Satisfaction)增强。

•觉得工作是一件愉快( Enjoyable )的事情。

什么样的人不适合做Pair Programming

太过自负

•不能容忍别人的意见

•我总是对的

•我吃盐多过你吃米

太过自卑

•没主见

•没责任心

什么样的人适合做Pair Programming

Extreme Programming对实施的程序员提出了更高的要求。这种要求不是技术水平,也不是学历水平也不是工作经验。这种要求是对一个人的心智,道德,修养的更高要求。

     程序员的四怕:

     1) 怕自己看上去傻

     2) 怕被认为是没用的

     3) 怕自己变的不重要(过时)

     4) 怕自己不够好

     Pair Programming中,编码不再是私人的工作,而是一种公开的“表演”。程序员的代码,工作方式,技术水平都变得公开和透明。

XPer的素质

一个XPer应该具备这样一些基本素质:诚实,公正,开明,勇敢和谦卑!在这些素质的基础之上,才是对技术水平,能力和天分等的要求。

•诚实 

•公正

•开明

•勇气

•谦卑

     具备这些素质才能克服“四怕”,才能成为一个成熟和专业的Developer。

如何Pair Programming

•Driver – 写设计文档(Class diagram等),进行编码(Unit Test and Business Object)等XP开发流程。

•Navigator – 审阅Driver的文档、Driver对编码等开发流程的执行;考虑Unit Test的覆盖程度;是否需要和如何Refactoring;帮助Driver解决具体的技术问题。

•Driver和Navigator不断轮换角色,不要连续工作超过一小时,每一小时休息15分钟。Navigator要控制开发时间。

•主动参与 – 虽然每个Engineering Task都有owner,但不能一旁观者的心态来做。任何一个Task都首先是两个人的责任,也是所有人的责任。没有“我的Code”、”你的Code”或“她的Code”,只有“我们的Code”。

•只有水平上的差距,没有级别上的差异。一个Pair,尽管可能大家的级别资历不同,但不管在分析,设计或编码,双方都拥有平等的决策权利。

•Pairs之间互换Partner。每个Task都应该和不同的Developer配对。

•每隔一天,甚至是半天,互换Partners。但Task的owner因该继续留该Task的Pair中。

•如果Pair中的一人请假,另一人应尽量不要写Production Code。

•Pair一起加班

没有Pair Programming就没有XP

•Pair Programming是XP所有的Practices中最被争议和被认为是最难接受。

•Pair Programming是获得XP最大价值的关键。

•没有Pair Programming,无法实现有效的Continuous Code Review,代码质量下降。

•没有Peer Pressure,流程的执行很容易出现偏差。

•没有Pair Programming,Communication很容易弱化,进而影响Team work。

•Pair Programming象XP流程中的粘合剂,把各个环节连接起来实现最大的价值。

XP Without Pair Programming?

这是引进XP时最难被接受的规则。但如果在采用其它XP的惯例和规则时,抛弃Pair Programming,那么会面对以下问题:

•如何进行有效的Design Review

•如何进行有效的Code Review

•如何保证代码质量

•如何保证流程的执行

•如何增进Communication

•如何进行Cross-Training

•如何增强Teamwork

Pair Programming和Open Source

Open Source现象:

Open Source Project的代码质量比很多的商业软件(项目)都好。

和Pair Programming的共性:

•有效的Code Review

•Collective  code ownership

Distributed Pair Programming

分布式的Pair Programming:

•两个Programmers身处不同的物理位置,通过Sharing 软件来实现Pair Programming。需要Sharing软件能提供 桌面共享,文字交谈,语音交谈,甚至是视频交流。

•目前这种方法还没有被认可,主要出现在学校的关于XP的研究项目中 。

面临的问题:

•Internet的网路延迟

•工作时段的约定

Pair Programming和Solo Programming的比较

虽然Pair Programming的学生在刚开始的阶段比独自工作的学生花在同样Task的时间较多,但很快Pair Programming的学生的时间开始大幅度的下降。而独立工作的学生需要花费比Pairs更多的时间来达到接近的代码质量。

比较研究项目后的问卷调查发现:

•Pair Programming能用较少的时间生产更高质量的代码。

•Pair Programming的学生们认为自己比一个人的时候更勤奋和更聪明的工作,因为不想让自己的partner失望。

•Pair Programming的学生认为自己比一个人的时候更专著,紧凑和由纪律的工作,而且是持续的(因为来自Partner的Pair-Pressure)。而独立工作的学生也可以专著和紧凑的工作,但往往不持续。

•Pair Programming的学生对自己的工作更有信心和成就感。

•Pair Programming的学生觉得工作很愉快,很愿意很partner一起工作。

•在紧张时间安排和繁重的工作压力下,独自工作的学生很容易蜕变为没有纪律的Programmer。

Pair Programming是个渐进的过程

有效率的Pair Programming不是一天就能做到的。

Pair Programming是一个相互学习,相互磨合的一个渐进过程。

Developers需要时间来适应这种新的开发模式。

刚开始的Pair Programming很可能不比Solo Programming有更高的

效率。但适应后的Pairs的开发质量,开发时间都应该比Solo

Programming有大幅度的改善。

Reference

•Kent Beck,Extreme Programming Explained:Embrace Change

•The Costs and the benefits of Pair Programming -- Alistair Cockburn & Laurie Williams

•Strengthening the Case for Pair Programming – Laurie Williams, Robert R. Kessler & Ward Cunningham

•PairProgramming.com

•www.chinaxp.org

原文来自http://www.sawin.cn/doc/SoftMethod/XP/

(一)规划的寓言:把一张纸折叠51次

  想象一下,你手里有一张足够大的白纸。现在,你的任务是,把它折叠51次。那么,它有多高?

  一个冰箱?一层楼?或者一栋摩天大厦那么高?不是,差太多了,这个厚度超过了地球和太阳之间的距离。

  心理点评

  到现在,我拿这个寓言问过十几个人了,只有两个人说,这可能是一个想象不到的高度,而其他人想到的最高的高度也就是一栋摩天大厦那么高。

  折叠51次的高度如此恐怖,但如果仅仅是将51张白纸叠在一起呢?

  这个对比让不少人感到震撼。因为没有方向、缺乏规划的人生,就像是将51张白纸简单叠在一起。今天做做这个,明天做做那个,每次努力之间并没有一个联系。这样一来,哪怕每个工作都做得非常出色,它们对你的整个人生来说也不过是简单的叠加而已。

  当然,人生比这个寓言更复杂一些。有些人,一生认定一个简单的方向而坚定地做下去,他们的人生最后达到了别人不可企及的高度。譬如,我一个朋友的人生方向是英语,他花了十数年努力,仅单词的记忆量就达到了十几万之多,在这一点上达到了一般人无法企及的高度。

   也有些人,他们的人生方向也很明确,譬如开公司做老板,这样,他们就需要很多技能———专业技能、管理技能、沟通技能、决策技能等等。他们可能会在一开 始尝试做做这个,又尝试做做那个,没有一样是特别精通的,但最后,开公司做老板的这个方向将以前的这些看似零散的努力统合到一起,这也是一种复杂的人生折 叠,而不是简单的叠加。

  切记:看得见的力量比看不见的力量更有用。

  现在,流行从看不见的地方寻找答案,譬如潜能开发,譬如成功学,以为我们的人生要靠一些奇迹才能得救。但是,在我看来,东莞恒缘心理咨询中心的咨询师毛正强说得更正确,“通过规划利用好现有的能力远比挖掘所谓的潜能更重要。”

(二)动机的寓言:孩子在为谁而玩

  一群孩子在一位老人家门前嬉闹,叫声连天。几天过去,老人难以忍受。

  于是,他出来给了每个孩子25美分,对他们说:“你们让这儿变得很热闹,我觉得自己年轻了不少,这点钱表示谢意。”

  孩子们很高兴,第二天仍然来了,一如既往地嬉闹。老人再出来,给了每个孩子15美分。他解释说,自己没有收入,只能少给一些。15美分也还可以吧,孩子仍然兴高采烈地走了。

  第三天,老人只给了每个孩子5美分。

 孩子们勃然大怒,“一天才5美分,知不知道我们多辛苦!”他们向老人发誓,他们再也不会为他玩了!

 心理点评:

  你在为谁而“玩”

  这个寓言是苹果树寓言的更深一层的答案:苹果树为什么会自断经脉,因为它不是为自己而“玩”。

  人的动机分两种:内部动机和外部动机。如果按照内部动机去行动,我们就是自己的主人。如果驱使我们的是外部动机,我们就会被外部因素所左右,成为它的奴隶。

  在这个寓言中,老人的算计很简单,他将孩子们的内部动机“为自己快乐而玩”变成了外部动机“为得到美分而玩”,而他操纵着美分这个外部因素,所以也操纵了孩子们的行为。寓言中的老人,像不像是你的老板、上司?而美分,像不像是你的工资、奖金等各种各样的外部奖励?

  如将外部评价当作参考坐标,我们的情绪就很容易出现波动。因为,外部因素我们控制不了,它很容易偏离我们的内部期望,让我们不满,让我们牢骚满腹。不满和牢骚等负性情绪让我们痛苦,为了减少痛苦,我们就只好降低内部期望,最常见的方法就是减少工作的努力程度。

  一个人之所以会形成外部评价体系,最主要的原因是父母喜欢控制他。父母太喜欢使用口头奖惩、物质奖惩等控制孩子,而不去理会孩子自己的动机。久而久之, 孩子就忘记了自己的原初动机,做什么都很在乎外部的评价。上学时,他忘记了学习的原初动机———好奇心和学习的快乐;工作后,他又忘记了工作的原初动机 ———成长的快乐,上司的评价和收入的起伏成了他工作的最大快乐和痛苦的源头。

  切记:外部评价系统经常是一种家族遗传,但你完全可以打破它,从现在开始培育自己的内部评价体系,让学习和工作变成“为自己而玩”。

(三)成长的寓言:做一棵永远成长的苹果树

  一棵苹果树,终于结果了。

  第一年,它结了10个苹果,9个被拿走,自己得到1个。对此,苹果树愤愤不平,于是自断经脉,拒绝成长。第二年,它结了5个苹果,4个被拿走,自己得到1个。“哈哈,去年我得到了10%,今年得到20%!翻了一番。”这棵苹果树心理平衡了。

  但是,它还可以这样:继续成长。譬如,第二年,它结了100个果子,被拿走90个,自己得到10个。

  很可能,它被拿走99个,自己得到1个。但没关系,它还可以继续成长,第三年结1000个果子……

  其实,得到多少果子不是最重要的。最重要的是,苹果树在成长!等苹果树长成参天大树的时候,那些曾阻碍它成长的力量都会微弱到可以忽略。真的,不要太在乎果子,成长是最重要的。

 心理点评

  你是不是一个已自断经脉的打工族?

  刚开始工作的时候,你才华横溢,意气风发,相信“天生我才必有用”。但现实很快敲了你几个闷棍,或许,你为单位做了大贡献没人重视;或许,只得到口头重 视但却得不到实惠;或许……总之,你觉得就像那棵苹果树,结出的果子自己只享受到了很小一部分,与你的期望相差甚远。

  于是,你愤怒、你懊恼、你牢骚满腹……最终,你决定不再那么努力,让自己的所做去匹配自己的所得。几年过去后,你一反省,发现现在的你,已经没有刚工作时的激情和才华了。

  “老了,成熟了。”我们习惯这样自嘲。但实质是,你已停止成长了。

  这样的故事,在我们身边比比皆是。

  之所以犯这种错误,是因为我们忘记生命是一个历程,是一个整体,我们觉得自己已经成长过了,现在是到该结果子的时候了。我们太过于在乎一时的得失,而忘记了成长才是最重要的。

  好在,这不是金庸小说里的自断经脉。我们随时可以放弃这样做,继续走向成长之路。

  切记:如果你是一个打工族,遇到了不懂管理、野蛮管理或错误管理的上司或企业文化,那么,提醒自己一下,千万不要因为激愤和满腹牢骚而自断经脉。不论遇到什么事情,都要做一棵永远成长的苹果树,因为你的成长永远比每个月拿多少钱重要。

(四)逃避的寓言:小猫逃开影子的招数

  “影子真讨厌!”小猫汤姆和托比都这样想,“我们一定要摆脱它。”

  然而,无论走到哪里,汤姆和托比发现,只要一出现阳光,它们就会看到令它们抓狂的自己的影子。

  不过,汤姆和托比最后终于都找到了各自的解决办法。汤姆的方法是,永远闭着眼睛。托比的办法则是,永远待在其他东西的阴影里。

  心理点评

  这个寓言说明,一个小的心理问题是如何变成更大的心理问题的。

  可以说,一切心理问题都源自对事实的扭曲。什么事实呢?主要就是那些令我们痛苦的负性事件。

  因为痛苦的体验,我们不愿意去面对这个负性事件。但是,一旦发生过,这样的负性事件就注定要伴随我们一生,我们能做的,最多不过是将它们压抑到潜意识中去,这就是所谓的忘记。

  但是,它们在潜意识中仍然会一如既往地发挥作用。并且,哪怕我们对事实遗忘得再厉害,这些事实所伴随的痛苦仍然会袭击我们,让我们莫名其妙地伤心难过,而且无法抑制。这种疼痛让我们进一步努力去逃避。

  发展到最后,通常的解决办法就是这两个:要么,我们像小猫汤姆一样,彻底扭曲自己的体验,对生命中所有重要的负性事实都视而不见;要么,我们像小猫托比 一样,干脆投靠痛苦,把自己的所有事情都搞得非常糟糕,既然一切都那么糟糕,那个让自己最伤心的原初事件就不是那么疼了。

  白云心理医院的咨询师李凌说,99%的吸毒者有过痛苦的遭遇。他们之所以吸毒,是为了让自己逃避这些痛苦。这就像是躲进阴影里,痛苦的事实是一个魔鬼,为了躲避这个魔鬼,干脆把自己卖给更大的魔鬼。

  还有很多酗酒的成人,他们有过一个酗酒而暴虐的老爸,挨过老爸的不少折磨。为了忘记这个痛苦,他们学会了同样的方法。

   除了这些看得见的错误方法外,我们人类还发明了无数种形形色色的方法去逃避痛苦,弗洛伊德将这些方式称为心理防御机制。太痛苦的时候,这些防御机制是必 要的,但糟糕的是,如果心理防御机制对事实扭曲得太厉害,它会带出更多的心理问题,譬如强迫症、社交焦虑症、多重人格,甚至精神分裂症等。

  真正抵达健康的方法只有一个———直面痛苦。直面痛苦的人会从痛苦中得到许多意想不到的收获,它们最终会变成当事人的生命财富。规划利用好现有的能力远比挖掘所谓的潜能更重要。”

  切记:阴影和光明一样,都是人生的财富。

  一个最重要的心理规律是,无论多么痛苦的事情,你都是逃不掉的。你只能去勇敢地面对它,化解它,超越它,最后和它达成和解。如果你自己暂时缺乏力量,你可以寻找帮助,寻找亲友的帮助,或寻找专业的帮助,让你信任的人陪着你一起去面对这些痛苦的事情。

   美国心理学家罗杰斯曾是最孤独的人,但当他面对这个事实并化解后,他成了真正的人际关系大师;美国心理学家弗兰克有一个暴虐而酗酒的继父和一个糟糕的母亲,但当他挑战这个事实并最终从心中原谅了父母后,他成了治疗这方面问题的专家;日本心理学家森田正马曾是严重的神经症患者,但他通过挑战这个事实并最终发明出了森田疗法……他们生命中最痛苦的

关于魔羯

“因为他们了解身边的朋友的所有性格,所以他们在包容对方,就算你做了什么过分的事,他们也早就考虑好对方为什么会这样做,最明显一点,你们可以去看看身边魔羯的朋友,无论你怎么做那些魔羯都不会很惊讶的,其实他们已经知道你为什么会这样了.魔羯的交友观也很随便,他们可能会和贵族很好,也可能会和乞丐聊天,一切的一切只是心灵的交往,很少有魔羯会有势力眼,除非你这个人人品太差了.”

所以,这就导致我自己的性格特征弱化,形象不那么鲜明。

所以,我得塑造个性了。

第8章 Continuing CXControl

译者:leexuany(小宝)

介绍:这就是《DirectX9 User Interfaces Design and Implementation》第8章的译文,让大家等了一个月,不好意思。这次小宝偷懒了,代码都没打全,想看的到我的资源(http://download.csdn.net/source/222788)里下电子书吧,需要0个积分。

正文:

图8.1

本章将继续UI LIB的开发,这是一个控件的集合,它包括按钮、列表框、复选框,甚至是窗口。下面我们要继续前一章剩余的工作,完成CXControl这个基类。我们将涉及到以下内容:

■事件处理

■窗口消息和自定义消息

■消息的传递和处理

■绘制

■深度优先遍历

■焦点

8.1 消息

为了随意的操纵控件,我们需要清楚地知道事件何时发生。例如,文本框需要对按键做出反应,按钮必须响应鼠标事件,等等。应用程序通过WndProc函数接收事件,这是消息循环的一部分。这里,事件以消息的形式被接收。实际上,当事件发生时它们作为参数传递给WndProc函数,它们是描述事件信息的数据包,比如键盘的按键情况和鼠标的新位置。下面定义的是windows消息的结构,表8.1列出了常见的消息和它们的描述。

struct Message

{

    UINT Message;      // 消息的类型,如鼠标消息等

    LPARAM Parameterl; // 描述事件的附加信息

    WPARAM Parameterw; // 描述事件的附加信息(注意,书上的这个变量名印错了)

};

表8.1 常用的消息

略,请参看原版电子书,我的资源中有(http://download.csdn.net/source/222788


8.1.1 传递消息

一旦WndProc接收到任何消息,它们就必须传递给各个控件。最特别的是,它们要传递给结构中的顶层控件。这样的控件一般是应用程序的主窗口。传递消息的过程被称为消息的分发,为了接收这些消息我们需要为CXControl添加PostMessage函数。它的声明和定义稍后分析。眼下我们知道一旦有消息到达这里,它们就必须向下分发给子孙控件,这是下一小节要讨论的话题。看看下面的WndProc函数来了解如何为控件选择并传递消息。

LRESULT WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

{

    switch(message)

    {

        case WM_MOUSEMOVE:

        case WM_KEYDOWN:

        // More messages here, you get the idea…

        Window->PostMessage(message, wParam, lParam, NULL);

        break;

    }

    return 0;

}

8.1.2 消息细节(Message Sepcifics)

消息用上面这个PostMessage方法传递给结构中的顶层控件。接着,它们以事件的形式传递给各个子控件。消息如何准确的向下传递以及谁接收它们主要取决于消息本身,这将在后面的小节中讨论。


8.2 处理鼠标消息

当鼠标状态改变的时候,结构中顶层的控件一般会接收到如下消息:

WM_LBUTTONDBLCLK

WM_LBUTTONDOWN

WM_LBUTTONUP

WM_MBUTTONDBLCLK

WM_MBUTTONDOWN

WM_MBUTTONUP

WM_MOUSEMOVE

WM_RBUTTONDBLCLK

WM_RBUTTONDOWN

WM_RBUTTONUP

注意,这并不是一个完整的列表,而且大多数的程序不处理鼠标中键的消息。随着消息的不同,这可能表示一个按钮按下、弹起,或者鼠标位置的改变。后面的小节演示了如何确认这些鼠标消息,以及如何将它们以事件的形式向下传递。下面的CXControl类新增了鼠标事件、PostMessage方法和其他几个函数。

class CXControl

{

protected:

// …

CXControl * PostToAll(UINT msg, WPARAM wParam, LPARAM lParam, void * Data);

}

8.2.1 光标插入点(Cursor Intersection)

一旦PostMessage方法收到鼠标消息,就有必要确认它们。由于鼠标行为的几何本质,鼠标的点击和移动只会影响到鼠标指针下面的控件并且不会超出它的边界(即,不会影响到此控件边界以外的其它控件)。因此最终只会有一个控件被告知此鼠标事件。为了确定一个控件是否可以接收某个鼠标事件,你必须检测鼠标是否在控件的矩形区域内。因此CXControl中添加了一个CursorIntersect函数用来判断输入的位置是否符合这个标准(就是判断鼠标是否在控件区域内)。

bool CXControl::CursorIntersect(FLOAT X, FLOAT Y)

{

D3DXVECTOR2 ControlAbsolutePos;

ControlAbsolutePos.x = 0;

ControlAbsolutePos.y = 0;

// ...

}

注意

这与第11章处理文本框中的插入符时对鼠标位置的解释有所不同。(译者注:在DOS的时代我们习惯将那个一闪一闪的竖线叫做“光标”,现在为了与鼠标指针相区别,已经改称为“插入符”了)


8.2.2 分级发送(Hierarchical Posting)

前面已经提到过,在某一时刻内只能有一个控件受鼠标的影响。换句话说就是,一次只能有一个控件位于鼠标指针的下方。因此,这就是那个能接受到鼠标事件的控件,它也被称作是目标控件。既然这样,目标控件就是画布与光标相交并且没有子孙控件或者没有与光标相交的子孙控件的控件。通常情况下,目标控件是结构中的最底层的控件。因此,确定目标控件就是一个排除的过程。这个过程从结构中的顶部开始并递归地向下移动,直到用相交测试找到目标控件为止。图8.2到8.5简要地示范了这个过程。

图8.2到8.5

下面我们为CXControl添加PostToAll成员函数。这是一个递归函数,作用是向结构中的子控件传递消息。在下一节中,我们将探寻如何将此运用于处理鼠标消息。

CXControl * CXControl::PostToAll(UINT msg, WPARAM wParam, LPARAM lParam, void * Data)

{

    CXControl * Temp = GetFirstChild();

    while(Temp)

    {

        CXControl * Next = Temp->GetNextSibling();

        if(Temp->PostMessage(msg, wParam, lParam, Data))

            return Temp;

        Temp = Next;

    }

    return NULL;

}

8.2.3 触发鼠标事件

我们的辛苦劳动就要得到成果了,这里我们要把鼠标消息转化成鼠标事件。在前面的章节中,我们研究过如何利用PostMessage把消息从WndProc传递到结构中的顶层控件,以及此函数如何触发派生类的适当事件,从而通过处理鼠标消息来实现此函数了。下面的代码演示了如何将从PostMessage接收的鼠标消息变得可用并分发给目标控件。

bool CXControl::PostMessage(UINT msg, WPARAM wParam, LPARAM lParam, void * Data)

{

    switch(msg)

    {

    case WM_LBUTTONDOWN:

        if(CursorIntersect(LOWORD(lParam), HIWORD(lParam)))

        {

            CXControl * Control = PostToAll(msg, wParam, lParam, Data);

            if(!Control)

                OnMouseDown(msg, LOWORD(lParam), HIWORD(lParam));

            return true;

        }

        else

            return false;

        break;

    }

}


8.3 处理键盘消息

当用户操作键盘的时候下面的键盘消息就会发生:

WM_KEYDOWN

WM_KEYUP

WM_CHAR

一个按键既可以按下也可以不按下。结果,处理键盘消息要比处理鼠标消息简单。击键不提供几何上可测量的数据。按键一般不像鼠标那样定义一个屏幕上的位置。因此,它们不能准确地告诉程序输入是为哪个控件准备的。这就是为什么要将焦点的概念应用于键盘消息的处理。基本上,拥有焦点的控件接受键盘消息。此外,在任何时刻,只能有结构中的一个控件获得焦点。后面的章节讨论了更多的关于焦点的细节。下面的代码展示了修正后的CXControl,增加了焦点的实现部分。

protected:

    bool m_Focus;

    // Focus control

    CXControl * m_Focus;

public:

    // Called when the user presses a key

    virtual void OnKeyDown(WPARAM Key, LPARAM Extended) = NULL;

    // Called when the user releases a key

    virtual void OnKeyUp(WPARAM Key, LPARAM Extended) = NULL;

    // Get focus control

    CXControl * GetFocus() {return m_Focus;}

    // Set focus control

    void SetFocus(CXControl * Control);

};

8.3.1 焦点

图8.6

拥有焦点的控件接收键盘消息。在任何时刻只能有一个控件拥有焦点。通过选择一个控件来把焦点转向它,换句话说就是,通过点击控件或者其他类似的过程来让控件获得焦点。从本质上来说就是,有一个控件获得焦点,就有一个控件失去它。下面的CXControl的成员方法展示了在层次中一直维持焦点是多么的奇妙以及焦点是如何从一个控件跳到另一个控件上去的。

void CXControl::SetFocus(CXControl * Control)

{

    if(!m_Focus)

    {

        if(GetParentControl())

            GetParentControl->SetFocus(Control);

        else

        {

            if(GetFocus())

                GetFocus()->m_Focus = false;

            m_FocusControl = Control;

            m_Focus = true;

        }

    }

}

注意:

实际上,当一个控件的孩子控件获得焦点的时候你不需要向父控件(指它本身)通报。但是这里已经这么做了。这样,当有消息传递给结构中的顶层控件时,拥有焦点的控件就可以直接接收输入的信息。下一小节中会谈论到这些。

8.3.2 处理事件

在使用了焦点的概念之后,把键盘消息转化为键盘事件就成了一个十分简单的过程。你可以简单地调用拥有焦点的控件的键盘事件就可以了。现在PostMessage成员方法添加进了处理键盘消息的代码。

CXControl * CXControl::PostToAll(UINT msg, WPARAM wParam, LPARAM lParam, void * Data)

{

    switch(msg)

    {

        // Handle other messages here…

    case WM_KEYUP:

    case WM_KEYDOWN:

        if(GetFocus())

        {

            if(msg == WM_KEYUP)

                GetFocus()->OnKeyUp(wParam, lParam);

            if(msg == WM_KEYDOWN)

                GetFocus()->OnKeyDown(wParam, lParam);

        }

        break;

    }

    return NULL;

}


8.4 处理控件绘制

最后需要考虑的,同时也是最重要的事件之一就是绘制。每当结构接收到WM_RENDER消息时,所有的控件都应接收到OnRender事件。这是一个自定义的消息用来代替WM_PAINT。它看起来和下面的句子类似:

#define WM_RENDER WM_USER+1

这个消息表示这个控件期待重绘自己,这个过程被称作是重绘。每个控件的外观都不一样,按钮一个样,而标签就是另外一个样子,等等。不同的控件如何绘制它们自己将在后面的章节中进行讨论。下面的代码是CXControl处理OnRender事件的一个实例。他演示了一个典型的控件如何使用CXTexture和CXPen类来绘制自己。后面的例子展示了绘制消息是如何通过结构传递给控件的。

bool CXTest::OnRender()

{

    D3DXVECTOR2 ControlAbsolutePos;

    ControlAbsolutePos.x = 0;

    ControlAbsolutePos.y = 0;

    GetAbsolutePosition(&ControlAbsolutePos);

    GetCanvas()->SetTranslation(&ControlAbsolutePos);

    GetPen()->DrawTexture(GetCanvas());

    GetCanvas()->SetTranslation(NULL);

}


提示

当窗口需要重绘的时候WndProc都会接收到WM_PAINT消息,尽管Direct3D在它自己的渲染程序中重绘窗口,但那是因为标准的窗口绘制太慢了。因此,WM_PAINT消息被一个自定义的WM_RENDER消息取代了,并且在Direct3D应用程序的渲染循环中手工传递到结构中去。如果你喜欢,你可以只是传递WM_PAINT消息,但是WM_RENDER更清楚、透明。


8.5 反过来传递

当WM_RENDER消息传递到结构中的时候,每一个控件都会接收到OnRender事件。绘制从结构的顶层开始,并逐层向下传递。这给人的感觉就好像是父控件要先于它的子控件绘制到屏幕上。换句话说就是,子控件晚于它们的父控件,或者说是绘制在父控件之上的。子控件接收到这个事件的顺序是重要的,因为它们要根据它们的z序列来绘制。需要特别指出的是,这里提到的序列是安排在三维空间中的。这个Z字序的方向是从屏幕后面指向你的,如图8.7。层次、控件还有它们的Z字序看起来就好像图8.8一样。

图8.7

图8.8

正如图8.8所示,右边的兄弟都有一个比左边的大的Z值。这意味着,右边的紧贴在它左边的兄弟后面。因此,结构中的兄弟需要从右往左绘制,而不是习惯上的从左向右。这确保了最左边的控件总是绘制在最前面。下面的代码是PostToAll函数的改写版本,叫做PostAllReverse。与从左向右传递消息不同的是,这个是从右向左传递的。

CXControl * CXControl::PostToAllReverse(CXControl * Control, UINT msg, WPARAM wParam, LPARAM lParam, void * Data)

{

    CXControl * Next = Control->GetNextSibling();

    if(Next)

        Next->PostToAllReverse(Next, msg, wParam, lParam, Data);

    Control->PostMessage(msg, wParam, lParam, Data);

    return NULL;

}


8.6 深度搜索(深度优先遍历?)

如果你的桌面包含三个不同的窗口,你可以通过在它们上点击一下来从它们中间进行选取。当你这样做的时候,被点击的窗口被前置,其它的被放到后面去。效果就是它们的Z字序改变了。下面的几幅图阐明了这些。

为了在结构中实现当前活动的窗口总是在它众多兄弟的前面,你必须确保活动的窗口总是最左边的窗口。如果不是这样,你必须重新调整这些控件以保持这个顺序。添加下面这个名叫MoveToFront的函数。它接受一个控件作为参数,并将其设为最左边的节点(控件)。

代码略。


8.7 触发绘制事件

结构中的所有的控件都从右到左接受一个OnRender事件。这是通过PostAllReverse方法完成从而确保控件按照正确的Z字序绘制。因此,PostMessage函数修改为下面的样子来处理WM_RENDER消息。请注意其中对鼠标点击处理做的细小变动。它已经可以为一个控件设置焦点并使用了MoveToFront方法。这确保当人们点击时控件被激活,换句话说就是,将其移动到最左边的节点,然后以最低的Z字序绘制。

代码略。


8.8 CXContrl最终的声明

代码略。


8.9 总结

这一章完成了基类CXControl。现在它已经包含了其它控件需要从它继承的所有东西,这些将在下一章编写CXWindow的时候用到。在继续之前,我们来回顾一下本章内容。

■消息是用来描述用户电脑上事件的数据结构。这包括绘制消息、鼠标消息以及键盘消息等。

■消息传递到结构的顶层控件,并以事件的形式向下传递给子控件。

■鼠标消息描述了与鼠标相关的消息,这包括按钮的按下、释放和光波的移动。

■一次只能有一个控件相应鼠标事件,并且以OnMouseMove、OnButtonDown、OnButtonUp等形式接收通告消息。

■键盘消息描述了与键盘相关的消息,这包括按键的按下与释放等。

■因为键盘不能像鼠标那样定义一个屏幕上的位置,所以它们不知道哪个控件接收输入。正因如此,我们引入了焦点的概念。

■拥有焦点的控件接收键盘输入,并且在任一时刻只能有一个控件拥有焦点。

■WM_PAINT消息表示控件需要绘制它们自己。

■控件应该按照它们的Z字序绘制自己。Z字序在三维空间上定义了控件与它的兄弟控件的层次关系。

★说明:复制表(只复制结构,源表名:a 新表名:b)

SQL: select * into b from a where 1<>1 


★说明:拷贝表(拷贝数据,源表名:a 目标表名:b) 


SQL: insert into b(a, b, c) select d,e,f from b; 


★说明:显示文章、提交人和最后回复时间 


SQL: select a.title,a.username,b.adddate from table a,(select max(adddate) adddate from table where table.title=a.title) b 


★说明:外连接查询(表名1:a 表名2:b) 


SQL: select a.f1, a.f2, a.f3, b.f3, b.f4, b.f5 from a left OUT JOIN b ON a.f1 = b.f3 (左连接)

SQL: select a.f1, b.f2 from a FULL OUT JOIN b ON a.f1 = b.f1 (全连接)


★说明:日程安排提前五分钟提醒 


SQL: select * from 日程安排 where datediff(‘minute’,f开始时间,getdate())>5 


★说明:两张关联表,删除主表中已经在副表中没有的信息 


SQL: 


delete from info where not exists ( select * from infobz where info.infid=infobz.infid )


★说明:四表联查问题: 


select f1, (select min(f)-1 from t where f>f1) as f2 from

(select f 1 as f1 from t where f 1 not in (select f from t) and f <(select max(f) from t)) as cc


★说明:– 


SQL: 


select A.NUM, A.NAME, B.UPD_DATE, B.PREV_UPD_DATE 

from TABLE1, 

(select X.NUM, X.UPD_DATE, Y.UPD_DATE PREV_UPD_DATE 

from (select NUM, UPD_DATE, INBOUND_QTY, STOCK_ONHAND 

from TABLE2 

where TO_CHAR(UPD_DATE,‘YYYY/MM’) = TO_CHAR(SYSDATE, ‘YYYY/MM’)) X, 

(select NUM, UPD_DATE, STOCK_ONHAND 

from TABLE2 

where TO_CHAR(UPD_DATE,‘YYYY/MM’) = 

TO_CHAR(TO_DATE(TO_CHAR(SYSDATE, ‘YYYY/MM’) || ‘/01’,‘YYYY/MM/DD’) - 1, ‘YYYY/MM’) ) Y, 

where X.NUM = Y.NUM ( ) 

and X.INBOUND_QTY   NVL(Y.STOCK_ONHAND,0) <> X.STOCK_ONHAND ) B 

where A.NUM = B.NUM 


★说明:– 


SQL: 


select * from studentinfo where not exists(select * from student where studentinfo.id=student.id) and 系名称=‘“&strdepartmentname&”‘ and 专业名称=‘“&strprofessionname&”‘ order by 性别,生源地,高考总成绩 


★说明: 


从数据库中去一年的各单位电话费统计(电话费定额贺电化肥清单两个表来源) 


SQL: 


select a.userper, a.tel, a.standfee, TO_CHAR(a.telfeedate, ‘yyyy’) as telyear, 

sum(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘01’, a.factration)) as JAN, 

sum(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘02’, a.factration)) as FRI, 

sum(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘03’, a.factration)) as MAR, 

sum(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘04’, a.factration)) as APR, 

sum(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘05’, a.factration)) as MAY, 

sum(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘06’, a.factration)) as JUE, 

sum(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘07’, a.factration)) as JUL, 

sum(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘08’, a.factration)) as AGU, 

sum(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘09’, a.factration)) as SEP, 

sum(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘10’, a.factration)) as OCT, 

sum(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘11’, a.factration)) as NOV, 

sum(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘12’, a.factration)) as DEC 

from (select a.userper, a.tel, a.standfee, b.telfeedate, b.factration 

from TELFEESTAND a, TELFEE b 

where a.tel = b.telfax) a 

group by a.userper, a.tel, a.standfee, TO_CHAR(a.telfeedate, ‘yyyy’

1.把某个字段重新生起序列(从1到n):  

 

set IDENTITY_INSERT Table1 ON

declare @i int

set @i = 0

update Table1 set @i = @i   1,Field1 = @i

set IDENTITY_INSERT Table1 off


2.按成绩排名次

update 成绩表

set a.名次 = (

select count(*)   1

from 成绩表 b

where a.总成绩 < b.总成绩

)

from 成绩表 a


3.查询外部数据库

select a.*

from OpenRowSet(‘Microsoft.Jet.OLEDB.4.0’,‘c:\test.mdb’;‘admin’;‘’,Table1) a


4.查询Excel文件

select *

from OpenDataSource(‘Microsoft.Jet.OLEDB.4.0’,‘Data Source=”c:\test.xls”;user id=Admin;Password=;Extended properties=Excel 8.0’)…Sheet1$


5.在查询中指定排序规则

select * from Table1 order by Field1 COLLATE Chinese_PRC_BIN

为什么要指定排序规则呢?参见:

http://www.zahui.com/html/8/15480.htm

例,检查数据库中的Pub_Users表中是否存在指定的用户:

select count(*) from Pub_Users where [UserName]=‘admin’ and [PassWord]=‘aaa’ COLLATE Chinese_PRC_BIN

默认比较是不区分大小写的,如果不加COLLATE Chinese_PRC_BIN,那么密码aaa与AAA是等效的,这当然与实际不符.注意的是,每个条件都要指定排序规则,上例中用户名就不区分大小写.


6.order by的一个小技巧

order by可以指定列序而不用指定列名,在下面的例子里说明它的用处(注意,第三列未指定别名)

select a.id,a.Name,(select count(*) from TableB b where a.id=b.PID) from TableA a order by 3


7.SQL简单分页的存储过程    ◆常用◆◆常用◆◆常用◆◆常用◆◆常用◆

/*

create proc recordpages

@nowpage int,

@per int

as

declare @s nvarchar(255)

if @nowpage<1 set @nowpage=1

if @per<1 set @per=1

set @s=Ndeclare @k int select top convert(varchar(10),(@nowpage-1)*@per) ‘ @k=id from table1

select top convert(varchar(10),@per) ‘ * from table1 where id>@k’

exec sp_executesql @s

go

*/

exec recordpages 3,10

8:得到表中最小的未使用的id

SQL: select * from a left inner join b on a.a=b.b right inner join c on a.a=c.c inner join d on a.a=d.d where ..... 

9.得到表中自动编号列精心策划号的起始位置


SQL: 

select (case when exists(select * from Handle b where b.HandleID = 1) then MIN(HandleID) + 1 else 1 end) as HandleID 

from Handle

where NOT HandleID in (select a.HandleID - 1 from Handle a)

孙悟空人生的第一次挫折是被压五行山,表面上看他是好名才受此大罪,为了官,为了名对抗天庭,连累了他的猴子猴孙,也连累了花果山上七十二洞结拜妖怪。可是他遭此大祸是命中注定的,菩提祖师曾经预测过他要遭受一次大祸,传授给他的七十二变,筋斗云,都是逃跑,伪装用的,可谓用心良苦。不愿受牵连的菩提祖师最后断了与悟空的师徒情分。悟空是很尊敬师父的猴子,也是只聪明的猴子,菩提祖师肯定在他心里占有重要的位置,可是他做了那么多惊天地的事,他以后还能见他吗?悟空这个名字也有意思,空是佛教里东西,道教里似乎讲无,《般若波若密心经》对空有段描述,“色不异空。空不异色。色即是空。空即是色”。什么是色,什么是空,每个人对它的理解不同。菩提祖师这里是道教,却给这只猴子一个有佛教含义的名,可能只有一个解释,他肯定预见到了悟空的未来。悟空,在五行山下悟到了什么是空?


   是谁把孙悟空从五行山解救出来的?不是唐僧,而是观世音菩萨。观音菩萨不只是去大唐寻找取经人,而且还去寻找护送唐僧的妖怪。白龙马,猪八戒,沙和尚这三个都是背景的人,猪八戒在天庭当过元帅,沙和尚在天庭当过将军,白龙马似乎是东海龙太子。在西天路上还有很多妖怪有背景,但观音菩萨却把最后一个名额给了毫无背景,而且曾经给天庭带来无数麻烦的孙悟空。观音菩萨是相当欣赏的孙悟空的,可能从他大闹天宫时就对这只猴子印象深刻,那时候天宫无人应战,观音菩萨推荐了二郎神,或者为了锻炼这只猴子,或者是给这只猴子一次机会,让他去战胜名气冲天的二郎神。但观音菩萨似乎并未观战就,直接回南海去了,观音菩萨一定知道如来会来收服他。观音菩萨也喜欢跟孙悟空开玩笑,在人参果那回里,孙悟空拿不动那只能救活树的葫芦,观音菩萨说:本来想让侍奉我的龙女跟你去,但怕你这泼猴见她貌美,花言巧语骗了去。让菩萨有心情开这样玩笑的可能就只有孙悟空了。


   从五行山下出来的孙悟空还痛恨如来佛吗?在西游记里可以看到,悟空在如来面前是很放肆的,不识礼节。单凭这点不能说明他还痛恨如来,在观音菩萨,灵吉菩萨,西天诸佛,玉皇大帝面前他也是这样。但这些位高权重,法力高强的神仙却容忍这只猴子,在真假美猴王这回中,六耳猕猴被孙悟空打死在如来佛面前,但如来只是怪悟空不该杀死六耳猕猴。佛教讲究不杀生,在西天诸佛面前,佛祖居然没有对犯下大戒的猴子加以惩罚,要知道唐僧的前世金蝉子只是在佛祖讲经时打了下瞌睡就被打发到人间来了。如来对悟空是宽容的。同样悟空也不会痛恨如来,尽管被压了五百年,这是由悟空的本性决定的,这是只不记仇的猴子。跟悟空有过节的神仙,妖怪太多了,但后来都和平相处了。如偷袈裟的黑熊精,最后跟随观音菩萨修行,养人参果的镇元大仙,最后跟悟空结拜成兄弟了。大闹天宫时,曾经和猴子大战的那些神仙在悟空有难时都会帮忙,象小西天那回,二十八星宿帮悟空从金钵中逃出,打九头虫时,悟空看到二郎神和梅山兄弟狩猎路过,不好意思直接打招呼,于是叫八戒去,二郎神听到悟空在这,于是赶紧下来与之相见,一起吃吃喝喝完了,帮悟空把九头虫干掉。二郎神杨戬是神与人的私生子,可能因为他长了三只眼,穿一身盔甲,威风凛凛,给人一种不能接近的感觉,现在的一些影视剧就喜欢把他塑造成高傲,自负的讨厌神仙。他绝对不是这样的。可惜自从出来《三国演义》,关云长就取代了他战神的地位。关云长倒是挺高傲,自负的。


   唐僧在揭下五行山上的偈语时,就对这只猴子不放心,说若是这只猴子是妖怪,骗他,就怎么样怎么样的。果然,一路西行路上多次怀疑猴子的判断,甚至把猴子赶走,在这多次怀疑中,猴子终于杀了不是强盗的凡人。当有次遇到一个漂亮女子时,悟空对唐僧说她是妖怪,当年在花果山也干过这种勾当,变成漂亮女子勾引人,然后杀之吃之。唐僧不相信女子是妖怪于是被妖怪所擒。应该不能嘲笑唐僧不识妖怪,不该嘲笑唐僧不听悟空劝说,因为猴子说谎了,唐僧只能相信自己的眼睛,但凡人的眼睛往往被事物表面所迷惑。猴子的谎话是说他在花果山吃人。悟空在拜菩提为师前就是一只普通猴子,没机会吃人,在拜菩提祖师后,也不会去吃人,象他这样的妖怪,用得着变成女子去勾引人,每天有猴子猴孙供着,不会愁吃,更重要的是变女子去勾引人影响他的名头啊。但猴子最后还是真杀人了,被唐僧赶走回到花果山,五百年后第一次回到花果山,由于当年的天兵放火烧山,花果山一片焦土,猴子猴孙被猎人捉走虐待,气得猴子把几十个猎人用石头全部砸死。吴承恩在写这段时也很同情那些猎人,以及那些猎人的家人。小说里的唐僧就不多说了。历史上的玄奘法师,也不是一般人,但不知道十几年后取经归来,路过高昌国,他心里是怎么想的。当年取经路过高昌,与高昌国王结拜为兄弟,但回来时才知道高昌国已经被大唐灭了。也许历史上的玄奘那时也会象小说中的悟空见到破败的花果山一样,哭了。

束发读诗书,修德兼修身,仰观与俯察,韬略胸中存。

躬耕从未忘忧国,谁知热血在山林。

凤兮,凤兮,思高举,世乱时危久沉吟。 

茅庐承三顾,促膝纵横论,半生遇知己,蛰人感举深。

明朝携剑随君去,羽扇纶巾赴征尘。

龙兮,龙兮,风云会,长啸一声舒怀襟。

归去,来兮,我夙愿,余年还做垅亩民。

清风明月入怀抱,猿鹤听我再抚琴。

天道常变易,运数杳难寻,成败在人谋,一诺竭忠悃。

丈夫在世当有为,为民播政太平春。

归去,来兮,我夙愿,余年还做垅亩民。

清风明月入怀抱,猿鹤听我再抚琴。

金属矿(Metal Mine)

 

    金属矿提供了新兴帝国所需的基本资源且被用以建造建筑物和生产船舰。金属是最便宜的资源,只需要少许能量就可以开采,但是它的使用量比其它资源都来的大。矿脉都位于地底深处,同时越深越大的矿消耗的能量也越多。

    除了星球的基本生产值之外,还可以藉由此建筑物来增加金属矿的产量,不过记得在盖这样建筑之前,必须先盖〔太阳能发电厂〕才有能源来供应此样建筑活动, 刚开始玩的新手,把金属矿跟晶体矿升级到等级八之后,就可以开始建其它的建筑了, 可以升两级金属矿之后再生一级晶体矿,之后让金属矿比晶体矿大两级就可以了。

 

资源需求:60金属15晶体 * 1.5 ^ ( 等级 - 1)

每小时产量 = 30 * 等级 * (1.1 ^ 等级)

能耗 = 10 * 等级 * (1.1 ^ 等级)

 

 

 

等级  产量  消耗重氢  需要金属  需要晶体  需要重氢  累计金属  累计晶体  累计重氢 

1  33  -11  60  15  60  15 

2  72  -24  90  22  150  37 

3  119  -39  135  33  285  71 

4  175  -58  202  50  487  121 

5  241  -80  303  75  791  197 

6  318  -106  455  113  1246  311 

7  409  -136  683  170  1930  482 

8  514  -171  1025  256  2955  738 

9  636  -212  1537  384  4493  1123 

10  778  -259  2306  576  6799  1699 

11  941  -313  3459  864  10259  2564 

12  1129  -376  5189  1297  15449  3862 

13  1346  -448  7784  1946  23234  5808 

14  1594  -531  11677  2919  34911  8727 

15  1879  -626  17515  4378  52427  13106 

16  2205  -735  26273  6568  78700  19675 

17  2577  -859  39410  9852  118111  29527 

18  3002  -1000  59115  14778  177227  44306 

19  3486  -1162  88673  22168  265900  66475 

20  4036  -1345  133010  33252  398910  99727 

21  4662  -1554  199515  49878  598426  149606 

22  5372  -1790  299273  74818  897699  224424 

23  6178  -2059  448909  112227  1346608  336652 

24  7091  -2363  673364  168341  2019973  504993 

25  8126  -2708  1010046  252511  3030020  757505 

26  9296  -3098  1515070  378767  4545090  1136272 

27  10619  -3539  2272605  568151  6817695  1704423 

28  12113  -4037  3408907  852226  10226603  2556650 

29  13800  -4600  5113361  1278340  15339964  3834991

 

 

 

 

 

 

 

 

 

晶体矿(Crystal Mine)

    晶体是电子组件和合金生产的主要资源。与金属的生产过程相比,将晶体资源纯化为工业用晶体的过程需要约两倍的能量;因此晶体在交易时较为昂贵。所有船舰和建筑物建造都需要晶体,可惜大多数飞船制造需要的晶体很少见,并且和金属矿一样必须要在地底深处才能采取到,越深的矿脉所能提供的晶体比浅层要来得多。

    除了星球的基本生产值之外,还可以藉由此建筑物来增加晶体矿的产量, 不过记得在盖这样建筑之前,必须先盖〔太阳能发电厂〕才有能源来供应此样建筑活动, 刚开始玩的新手,把金属矿跟晶体矿升级到等级八之后, 就可以开始建其它的建筑了。

 

资源需求:48金属24晶体 * 1.6 ^ ( 等级 - 1)

每小时产量 = 20 * 等级 * (1.1 ^ 等级)

能耗 = 10 * 等级 * (1.1 ^ 等级)

 

等级  产量  消耗重氢  需要金属  需要晶体  需要重氢  累计金属  累计晶体  累计重氢

1  22  -11  48  24  48  24 

2  48  -24  76  38  124  62 

3  79  -39  122  61  247  123 

4  117  -58  196  98  444  222 

5  161  -80  314  157  758  379 

6  212  -106  503  251  1262  631 

7  272  -136  805  402  2067  1033 

8  342  -171  1288  644  3355  1677 

9  424  -212  2061  1030  5417  2708 

10  518  -259  3298  1649  8716  4358 

11  627  -313  5277  2638  13993  6996 

12  753  -376  8444  4222  22437  11218 

13  897  -448  13510  6755  35948  17974 

14  1063  -531  21617  10808  57566  28783 

15  1253  -626  34587  17293  92153  46076 

16  1470  -735  55340  27670  147493  73746 

17  1718  -859  88544  44272  236038  118019 

18  2001  -1000  141670  70835  377709  188854 

19  2324  -1162  226673  113336  604382  302191 

20  2690  -1345  362677  181338  967060  483530 

21  3108  -1554  580284  290142  1547345  773672 

22  3581  -1790  928455  464227  2475800  1237900 

23  4118  -2059  1485528  742764  3961328  1980664 

24  4727  -2363  2376844  1188422  6338173  3169086 

25  5417  -2708  3802951  1901475  10141124  5070562 

26  6197  -3098  6084722  3042361  16225847  8112923 

27  7079  -3539  9735556  4867778  25961404  12980702 

28  8075  -4037  15576890  7788445  41538294  20769147 

29  9200  -4600  24923024  12461512  66461319  33230659

 

 

 

 

 

 

 

 

 

重氢同步分离器(Deuterium Synthesiser)

 

    重氢,也就是重水 -- 在氢原子核内多包含了一颗中子,由于氘-氚聚变反应(即一般所知的核融合反应)所带来的高能量,使其非常适合做为船舰的燃料。由于其分子较重的缘故,重氢常可在深海中被发现;将重氢同步分离器升级可以增加重氢的产量。

    超过一半的研究,以及机器人工厂,造船厂跟研究实验室都需要重氢, 在金属矿跟晶体矿都抵达八等级之后,也应该把此样建筑升级到五等级, 依照目前的资源产量,应该可以负担得起七等级升级八等级所需要的资源,但是相对的,八等级重氢同步分离器所需要的能源, 必须要十二级的太阳能发电厂才行,十一级太阳能发电厂升级到十二级是很庞大的负担,所以先升到五等级就好。

 

资源需求:225金属75晶体 * 1.5 ^ ( 等级 - 1)

每小时产量 = 10 * 等级 * (1.1 ^ 等级) * (-0.002 * 最高温度 + 1.28)

能耗 = 20 * 等级 * (1.1 ^ 等级)

 

等级  产量  消耗重氢  需要金属  需要晶体  需要重氢  累计金属  累计晶体  累计重氢 

1  11  -22  225  75  225  75 

2  24  -48  337  112  562  187 

3  39  -79  506  168  1068  356 

4  58  -117  759  253  1828  609 

5  80  -161  1139  379  2967  989 

6  106  -212  1708  569  4675  1558 

7  136  -272  2562  854  7238  2412 

8  171  -342  3844  1281  11083  3694 

9  212  -424  5766  1922  16849  5616 

10  259  -518  8649  2883  25499  8499 

11  313  -627  12974  4324  38473  12824 

12  376  -753  19461  6487  57935  19311 

13  448  -897  29192  9730  87128  29042 

14  531  -1063  43789  14596  130918  43639 

15  626  -1253  65684  21894  196602  65534 

16  735  -1470  98526  32842  295128  98376 

17  859  -1718  147789  49263  442917  147639 

18  1000  -2001  221683  73894  664601  221533 

19  1162  -2324  332525  110841  997127  332375 

20  1345  -2690  498788  166262  1495915  498638 

21  1554  -3108  748182  249394  2244098  748032 

22  1790  -3581  1122274  374091  3366372  1122124 

23  2059  -4118  1683411  561137  5049783  1683261 

24  2363  -4727  2525116  841705  7574900  2524966 

25  2708  -5417  3787675  1262558  11362575  3787525 

26  3098  -6197  5681512  1893837  17044088  5681362 

27  3539  -7079  8522269  2840756  25566357  8522119 

28  4037  -8075  12783403  4261134  38349761  12783253 

29  4600  -9200  19175105  6391701  57524867  19174955

 

太阳能发电厂(Solar Plant)

 

    为了给建筑物提供足够能量,需要大型的太阳能发电厂。太阳能发电厂是产生能量的一种方式,它使用了以半导体所构成的光伏电池(太阳能电池)进行光电转换。当太阳能发电厂的等级提升,它需要更大的空间安置太阳能面板以提供更多的能量。太阳能发电厂是星球上所有建设的基础。

    新手第一个必须要建的建筑物,有了这样建筑, 才能产生金属矿ˋ晶体矿以及重氢同步分离器活动所需要的能源, 右上方的剩余能量值最好保持在零以上(剩余能量值/总能量值), 如果小于零变成负号,资源的生产额就会下降。

 

资源需求:75金属30晶体 * 1.5 ^ ( 等级 - 1)

每小时发电量 = 20 * 等级* (1.1 ^ 等级)

 

 

等级  产量  消耗重氢  需要金属  需要晶体  需要重氢  累计金属  累计晶体  累计重氢 

1  22  75  30  75  30 

2  48  112  45  187  75 

3  79  168  67  356  142 

4  117  253  101  609  243 

5  161  379  151  989  395 

6  212  569  227  1558  623 

7  272  854  341  2412  965 

8  342  1281  512  3694  1477 

9  424  1922  768  5616  2246 

10  518  2883  1153  8499  3399 

11  627  4324  1729  12824  5129 

12  753  6487  2594  19311  7724 

13  897  9730  3892  29042  11617 

14  1063  14596  5838  43639  17455 

15  1253  21894  8757  65534  26213 

16  1470  32842  13136  98376  39350 

17  1718  49263  19705  147639  59055 

18  2001  73894  29557  221533  88613 

19  2324  110841  44336  332375  132950 

20  2690  166262  66505  498638  199455 

21  3108  249394  99757  748032  299213 

22  3581  374091  149636  1122124  448849 

23  4118  561137  224454  1683261  673304 

24  4727  841705  336682  2524966  1009986 

25  5417  1262558  505023  3787525  1515010 

26  6197  1893837  757535  5681362  2272545 

27  7079  2840756  1136302  8522119  3408847 

28  8075  4261134  1704453  12783253  5113301 

29  9200  6391701  2556680  19174955  7669982

 

 

 

 

 

 

 

 

 

 

核融合发电厂(Fusion Plant)

 

    在极度的高温及高压下,核融合反应炉将两个氘原子进行聚合而生成一个氦原子。当两个氘原子核融合成α粒子(氦原子)时,其总质量会有所损耗。质量并不会轻易消失,而是被转变成新创造出来的原子的能量。这些能量可以被转换成电力。每一公克的重氢可以提供172MWh(千度)的能量;所以核融合发电厂越大,同时间就可以进行更多并列的核融合反应,以提高总输出能量。

    目前新手是无法看到这一样建筑物,因为条件还未达到, 必须要〔重氢同步分离器〕五级以及〔能量技术〕三级才能建造,该建筑能量产额远大于同等级的太阳能发电厂, 相对的升级到同等级的资源也消耗更多, 而且还必须以重氢为原料来产生能源,若不是必要请不要一直升级该建筑。

    核电厂拿来当主力资源十分不划算,因为消耗重氢很多,建筑所需资源也很高。 但是可以稍微盖个三级左右,补足稍微有点差距的资源数量。

    核融合电厂会消耗重氢,经过计算,它的最大效能依然略小于太阳能电厂。重氢消耗随温度提高而减少,但在高温星球盖核融合发电厂依然是不划算的。 核融合发电厂的应用为能量不足时之临时处置。在某些情况下,3级核融合发电厂能补充临时短缺的能量直到太阳能发电厂升级为止。 核融合发电厂还有其它应用,但使用前必须经过仔细的评估。

    在相同占面积下,以核电为主力效率不如太阳能电厂(面积包含须补足的重氢)。但是到中期金晶短缺,星球空间也充裕的情况下,拿一点重氢换几百能量是很好的投资。等到面积不够再拆就可以了(怕面积不够一开始就选大一点的殖民星)。 太阳能电厂盖到一定程度会卡资源盖不上去,但是资源厂还有向上发展空间。在这段期间用核电去拼高资源厂也是划算的,因为说会亏损是因为拿核电与同等级的重氢去计算。

有人观察发现,核电消耗的重氢是账面上的88%。 还有人指出将重氢产率调0,并带走所有重氢,核电依然正常运作。 有兴趣的人可以测试看看。

资源需求:900金属360晶体180重氢 * 1.8 ^ ( 等级 - 1)

每小时发电量 = 20 * 等级* (1.1 ^ 等级)

重氢消耗 = 10 * 等级 * (1.1 ^ 等级)

  

 

 

机器人工厂(Robotic Factory)

    机器人工厂提供便宜且确实的劳动力用以进行基础建设。每提升一级,建筑物升级的速度也就越快。

    机器人工厂可以让所有”建筑”的建筑时间减少, 不过每等级的机器人工厂减少的时间都不一样, 把机器人工厂升级到N等级,建筑减少的时间为N/N+1, 也就是说越高等的机器人工厂,升级之后减少的建筑时间越少。

    在后期,机器人工厂可以让建筑提早结束,所缩短的时间中增加的收入,是可能可以补足盖机器人工厂所需要的资源的。

    资源需求:金属400晶体120重氢200 * 2 ^ 等级

    建筑物制造时间: (单位︰小时)

  [ (所需晶体+金属) / (2500 * (1 + 机器人工厂等级)) ] * 0.5 ^ 奈米机器人工厂等级

研究时间: (单位︰小时) 

(所需金属+晶体) / (1000 * (1 + 实验室等级))

 

间谍技术(Espionage Tech)

    用来提高你间谍跟反间谍的能力,关系到你间谍人能看到多少信息, 2级以上能得知来犯舰队的飞船的总数, 4级以上能得知来犯舰队的飞船的总数和种类, 8级以上能得知来犯舰队的飞船的总数和种类和每种飞船的数目。

    间谍探测技术主要是研究数据感应器和智能型装置与知识,以供探测数据并防止外来的间谍探测。这项技术的等级越高,就能从其它帝国的行星获得更多数据。

    间谍卫星探测数据的多寡,主要取决于自己和对手的间谍技术的差距。自己的技术等级越高,就能获得更多资料且被发现的机率也越低。发送的间谍卫星越多,就能回传更多讯息-但此举也大大提高了被发现的机率。

    提升间谍探测技术也可以得知关于接近自己星球的舰队数据:

    - 等级2可以看到舰队总数

    - 等级4可以区分出舰队内有哪些种类的船舰

    - 等级8可以分辨各种船舰各有几艘。


    一般来说,无论是侵略性的或爱好和平的,间谍探测技术对每个星际帝国都很重要。最好在小型运输机研究好之后就对它进行发展。 

    资源需求:200金属1.000晶体200重氢

 

计算机技术(Computer Tech)

    计算机技术研究用来提高计算机的计算能力。研究出更高性能更有效的控制系统。每一等级的提升都增强了运算能力和数据的平行处理能力。计算机技术的提升能指挥更多的舰队。每次出发的舰队越多,能攻击的也就越多,带回的资源也越多,当然这项技术也被商人利用,因为他能让更多的商业舰队出发。因此计算机技术应该在游戏中不断的升级。

    计算机用来增加你舰队的发射数量,你的舰队发射数等于计算机技术的等级+1,建议点高一点,这样逃命、间谍、抢人、运物资,都很方便。

    资源需求:0金属400晶体600重氢

 

武器技术(Weapons Tech)

    武器技术研究如何让现有的武器系统产生更大的破坏力。 它主要是着重于让武器能更有效的利用能量,发挥更佳的效能。

    如此一来每提升一级技术,相同的武器拥有更多能量,攻击力也越强-每提升一级,武器攻击力在基础值增加10%。 由于武器技术能让你保持和敌人之间的优势,因此你应该在游戏中持续的升级武器技术。

    Ogame 的技术都是提升基础值,也就是一级的时候为 10%,二级的时候为基础值增加20%。

    资源需求:800金属200晶体0重氢

 

防御盾系统(Shielding Tech) 

    防御盾系统用来在你的船舰周围产生防护性的粒子护盾。每提升一个等级可以为防护盾增加10%的效率。等级提升增加了护盾能量总额,使它在崩溃前能够吸收更多的能量。防御盾装置不仅被使用在船舰上,在行星防御圆顶上也能见到它的踪影。

    资源需求:200金属600晶体0重氢

 

飞船装甲化(Armour Tech)

    特殊的合金使装甲更加强大。 一旦一种十分强固的合金被找到,就会被特殊的射线改变船舰壳体的分子结构从而达到合金最好的状态。装甲的效力在每升一级飞船装甲化后在基础值上升10%。

    用来增加你舰队跟炮台的各种数值, 能点多高就点多高, 不过最好等到你有意发展武装舰队, 而且资源充裕的时候点高。

    资源需求:1.000金属0晶体0重氢

 

能量技术(Energy Tech)

    能量技术致力于发展对能量系统和能量储存技术的开发和研究: 当技术等级提升,你的系统便越有效率。从能量技术上获得的知识将成为研究其它特定技术的基础。

    各种技术的先决条件,点到想要研发的基础数值就够了。

    资源需求:0金属800晶体400重氢

 

超空间科技(Hyperspace Tech)

    通过结合了4次元和5次元的推进技术,可以创造出一种新的引擎系统 - 它的效率更高,更节省燃料。超空间科技提供了大型战舰和空间跳跃门进行超空间跳跃所需的基本科技。这种崭新而复杂的技术需要昂贵的实验设备和测试用的设施。

    超空间推动的前置作业,一样点基础数值就行了。

    资源需求:0金属4.000晶体2.000重氢

 

燃烧技术(Combusion Engine)

    基于反作用力,燃烧引擎是最古老的引擎种类。高温的粒子被高速的甩出飞船并以此来推动飞船向反方向前进。燃烧引擎的效能低落,但它便宜而容易操作。它们的体积很小,适合小型船舰,操作过程中也不需耗费太多计算机系统资源。

    每提升一个等级可以在基础值上增加10%的速度。

    这个技术是连系星际帝国最基本的方式,它应该被尽早研究。

    关系到你小型运输机、大型运输机、轻型战斗机、间谍卫星跟回收船的速度,每升一级加基本速度的10%,要点多少见仁见智,不过最少要等级6,大型运输机的基本要求。

    资源需求:400金属0晶体600重氢

 

脉冲引擎(Impulse Engine)

    脉冲引擎基于反作用力的原理而设计。此种推进系统所用的燃料是核融合炉生产能量后产生的垃圾。与简单的燃烧引擎比较之下,脉冲引擎更为先进,可以用较少的燃料消耗量获得较高的速度。每升一级提高速度20%。

    关系到你的巡洋、重型战斗机、殖民飞船跟导弹舰的速度,每升一级加基本速度的20%,最少点到3(殖民飞船底限),不过点到的话5,可以让你的小型运输机换引擎(非法改装!?) 速度飙超快,很诱人的一件事。

    资源需求:2.000金属4.000晶体600重氢

 

超空间引擎(Hyperspace Engine)

    通过对时间和空间的弯曲从而使船舰周围的空间进行压缩,从而使飞行的距离减少。这项技术水平越高,空间压缩的也越剧烈,从而达到提高速度的目的。每升一级提高速度30%。关系到你战列、毁灭者跟死星的速度,每升一级加基本速度的 30%,最少点到4,不过点到8可以让你的导弹舰换引擎,速度一样会有所提升。

    资源需求:40.000金属80.000晶体24.000重氢

 

激光技术(Laser Tech)

    雷射(LASER - Light amplification by stimulated emission of radiation) 是高能量的光子束,具有指向性和绝佳的聚焦性质。 雷射装置的用途很广泛: 从导航陀螺仪,光学计算机或武器系统,激光技术对每个帝国来说都是基础知识。

    激光技术完全不会影响你的攻击火力,所以不要以为点高会变强,最主要点他是为了盖轻雷跟等离子,够用就好。

    资源需求:200金属100晶体

 

中子技术(Ion Tech)

    中子武器技术藉由将高加速的中子光束投射在目标上,依照目标物带电荷的本质,可以造成巨大的伤害。中子光束比雷射光优秀,但需要更多的研究费用。虽然与其它技术相较之下较为简易,在大多数的星球上,被运用到的机会并不大。

    中子技术完全不会影响你的攻击火力,所以不要以为点高会变强,点他的目的,只有巡洋跟导弹舰的前置作业吧,也是够用就好。

    资源需求:1.000金属300晶体100重氢

 

等离子技术(Plasma Tech)

    由于等离子不友善的性质,等离子武器比任何已知的武器系统都要来得危险。 等离子是物质四态的其中之一 (固态,液态,气态,等离子[电浆态]),是由带正电荷和带负电荷的离子所组成的流体。只要输入的能量足够,原本为电中性的气体会分离成为各带有正负电荷的离子和电子。 利用磁力技术,这些带电的粒子被包裹成"球状" 以便发射。

    等离子技术完全不会影响你的攻击火力,所以不要以为点高会变强,用来盖离子炮跟导弹舰的技术,相当诱人而且昂贵,够用就好。

    资源需求:2.000金属4.000晶体1.000重氢

 

跨银河研究网络(Intergalactic Research Network) 

    你的星球上的科学家可以经由网络互相通信。每提升一个等级,网络会自动将未联机的研究实验室加入网络中。在联机建立后,它们的等级会相加。每个连结中的研究实验室都需要有相应的等级以加入研究。只要等级足够,研究实验室会各自分担工作,效率就像它们被相加一样。

    每增加一级, 就可以把其它殖民星球里,最高级的实验室加入研究网络中,此时他们的研究等级会相加, 但是你要研究的项目, 如果在联机星球的研究所等级太低, 不够达到研究该技术的等级的话, 就没有效果。

    意思是说,你这科技每升一级,你在进行研究时,可以把进行研究的行星之外的星球中,研究所等级最高的那一颗的研究所等级也加上来。但要那行星上的研究所等级也大于该科技要求。

    例如,你有4个行星有研究所,分别是:10、8、5、4,而你要研究武器技术。

    当你使用10级的那颗行星进行研究时, 如果你这科技等级是1,那你可以把8级的加上来,等于以10+8 =18的等级进行研究。如果你这科技等级是2,那你可以把8级和5级的加上来,等于以10+8+5=23的等级进行研究。如果你这科技等级是3,那你可以把8级、5级和4级的全加上来,等于以10+8+5+4=27的等级进行研究。

    但是,如果你进行研究的那行星研究所等级不到你要研究科技的要求,就不能研究。举个例子,如超空间推动要7级研究所才能研究。而你己经有了这个科技一级,而你也有二个行星的研究所等级是6、5,相加是11,超过7级。但你还是不能研究超空间推动。 而当你把6级的升级成7级之后,你就可以研究超空间推动,但是还是以7级研究所等级来研究。你必需把另一个行星的研究所也升级到7级,这样就会以7+7 = 14级来进行研究。

    资源需求:240.000金属400.000晶体160.000重氢

 

引力研究(Graviton Technology)

    重力子是产生重力的基本微粒。它是自己的反粒子,没有质量,不带电荷,自旋数为2。

    通过发射密集的重力微粒,人工的重力场被制造出来,其能量和吸引力不只可以摧毁船舰,甚至是月球。为了产生足量的重力微粒,需要大量的能量。

    需求: 研究实验室 (等级12)

    研发他只有一个目的,就是为了我们的终极目标,死星! 不过所费相当相当高啊... 要价0金属 0晶体 0

小型运输机(Small Cargo Ship)

    小型运输机的大小和战斗机差不多,但是它们没有高效率的引擎和军用装备,而是把空间挪出来做为货仓。小型运输机可以装载5000单位的资源。大型运输机的搭载量是它的五倍,装甲,防护盾和引擎也都有提升。由于火力薄弱,运输舰需要其它的船舰护航。

    当脉冲引擎研发到5级时,小型运输机将会换装此型引擎,并增加速度。

快速射击对 间谍卫星: 5

快速射击对 太阳能卫星: 5

死星 对这种船舰的快速射击: 250

    经由这次的改版,从原本的克难初期使用物品中跳脱,成为非常有潜力的船。

 

 

大型运输机(Large Cargo Ship)

    由于它的空间都被拿来作为货仓,无法搭载高等级的武器系统和其它科技,这类船舰不应该单独行动。使用高出力的燃料引擎,它成为快速的资源后勤单位,在星球之间穿梭。当然它也伴随着舰队攻击敌方星球,从而能掠夺更多的资源。

快速射击对 间谍卫星: 5

快速射击对 太阳能卫星: 5

死星 对这种船舰的快速射击: 250

    改版前后都必备用的东西,其高容量跟c/p值,还有不错的速度,每个人都应该有一定的数量。

 

轻型战斗机(Light Fighter)

    与它薄弱的装甲和火力相比,轻型战斗机在战争中扮演着一种支持性的角色。凭着它们的灵活度,速度以及数量,轻型战斗机常被用来保护那些较大而笨重的船舰。

快速射击对 间谍卫星: 5

快速射击对 太阳能卫星: 5

巡洋舰 对这种船舰的快速射击: 3

死星 对这种船舰的快速射击: 200

    炮灰I型机,在前期这是你第一个可以用来K人的玩意,等战列出了之后,摇身一变成为肉盾。

    早期使用的船只,因为便宜而且不需要研究太多科技就可以使用。迅速不过脆弱。在后期的游戏中当作炮灰使用。

 

重型战斗机(Heavy Fighter)

    在轻型战斗机的改进研究中,研究人员发现用传统的推动技术已经不能满足要求。为了提供新型的战斗机更好的灵活度,脉冲引擎第一次被使用。使用了高价值的材料,虽然提高了费用和复杂度,却也提供更多的可能性。使用脉冲引擎,可以提供武器和防护盾系统更多的能量。强化过的机身结构和更强悍的火力,使得这种战斗机比它的前辈更有威胁性。

    快速射击对 间谍卫星: 5

    快速射击对 太阳能卫星: 5

    死星 对这种船舰的快速射击: 100

    炮灰I型豪华精装版,笔者我根本没盖过他,以100战列为底,仿真结果显示,用同样资源数的轻型战斗机-战列编队,杠上重型战斗机-战列编队,重型战斗机-战列编队败战。

    比轻型战斗机更强一些,当别的玩家的殖民地只有些许导弹和雷射炮防守时,使用重型战斗机是个好选择。在后期的游戏中几乎没有用处。

 

巡洋舰(Cruiser)

    随着重型雷射炮和离子加农炮被运用在战场上,战斗机陷入了一个困境。在这些新型的防御系统前,无论怎么提升武器强度和装甲,战斗机依旧无法与其对抗。

    因此人们决定发展一种新的船舰,它要有更高的火力和防护。 巡洋舰就此诞生了。战斗巡洋舰的装甲大约是重型战斗机的3倍,火力强2倍。它的速度也是所有船舰中最快的。对于中度的防御没有比他更好的武器了。巡洋舰几乎统治了整个宇宙有100年。

    不幸的是,随着高斯加农炮和等离子发射器等新型防御设施被开发出来,巡洋舰结束了它的辉煌历史。由于武器系统效能适合,现在巡洋舰仍被用来对抗大量的战斗机中队。

    快速射击对 间谍卫星: 5

    快速射击对 太阳能卫星: 5

    快速射击对 轻型战斗机: 3

    快速射击对 飞弹发射器: 10

    死星 对这种船舰的快速射击: 33

    这是你第一台可以称为真正的武力的船,他对飞弹发射器跟轻型战斗机的高抵抗性,很适合用来辗级数比你低的人,后期也可以用他对抗轻型战斗机海。

    你能建造的第一个”大”船。因为有快速射击的能力,非常适合拿来消灭炮灰部队,例如轻型战斗机和导弹。拥有最高的基本速度。在后期的游戏中可以拿来对抗敌人的炮灰。不擅长对付毁灭者和战列。

 

战列舰(Battleship)

    战列舰是舰队的脊梁。它的重型防护,高速,超大的装载空间使得这种船舰被公认为是最优秀的船舰。巨大的货物仓也使得战列舰适于进行掠夺任务。

快速射击对 间谍卫星: 5

快速射击对 太阳能卫星: 5

死星 对这种船舰的快速射击: 30

    真正的攻击火力,他的防护,装载量与攻击力,是武装舰队不可或缺的,而且他不用重氢就能量产,常跟轻型战斗机一起搭档。

    Ogame中最常用到的船舰。快速、便宜(就他的战斗能力来说)、威力强大。对抗炮灰的时候没有快速射击能力是它的唯一缺点。所以需要建立一支混编的舰队,以战列舰为战斗基础。当你攻击大目标的时候,最佳的使用方法是放在炮灰后面。不需要重氢就能建造,掠夺战法的完美选择。

 

殖民飞船(Colony Ship)

    殖民飞船是特殊设计过的船只,拥有厚重的装甲,允许帝国开拓并殖民到新天地。它将用来对新的星球资源的提供,能整个被分拆然后被重新利用,以建造一个全新的世界。每个帝国能殖民包括主行星在内最多9个星球。

    快速射击对 间谍卫星: 5

    快速射击对 太阳能卫星: 5

    死星 对这种船舰的快速射击: 250

    殖民用,有了他,你才能更进一步的增加星球,详细说明请参阅殖民篇。

 

 

回收船(Recycler)

    太空战斗的规模越来越庞大,在单一次战斗中总有着成千上万的船舰被摧毁,产生的船舰废墟却白白被放弃。普通的运输舰无法收集这些珍贵的资源,甚至于无法接近。

    通过防护技术的发展,产生了一个新的舰艇种类,有了它的帮助能把巨大的流失的资源回收。回收船的大小与大型运输机相仿,但额外搭载的防护装备占据了一些可用空间。因此,回收船的可用储存空间被限制为20.000。

 

快速射击对 间谍卫星: 5

快速射击对 太阳能卫星: 5

死星 对这种船舰的快速射击: 250

用来回收「战场废墟(TF)」,任务请用「回收」,不过其龟速常令人空手而回。

 

间谍卫星(Espionage Probe)

    间谍卫星是小巧灵活的探测器,搭载高效能的推进机构,它能够提供遥远的星球上的讯息。使用先进的通讯系统,这些探测器可以从极为遥远的地方回传搜集到的情报。

    在抵达目标行星的绕地轨道后,探测器会运行轨道一周以搜集情报,此时它们很容易被发现。为了节省空间缩小体积,防护盾和武器被放弃了。只要一被发现,间谍卫星通常会因为薄弱的结构而被摧毁。

 

小型运输机 对这种船舰的快速射击: 5

大型运输机 对这种船舰的快速射击: 5

轻型战斗机 对这种船舰的快速射击: 5

重型战斗机 对这种船舰的快速射击: 5

巡洋舰 对这种船舰的快速射击: 5

战列舰 对这种船舰的快速射击: 5

殖民飞船 对这种船舰的快速射击: 5

回收船 对这种船舰的快速射击: 5

导弹舰 对这种船舰的快速射击: 5

毁灭者 对这种船舰的快速射击: 5

死星 对这种船舰的快速射击: 1250

    探查对方用,任务请用「间谍」,为消耗品,遇到有船舰停留的星球很容易变成战场废墟,有资源的话多弄几颗备用。

    能获得的信息跟你派的间谍数量及你的间谍技术相关,你能获得的谍报信息程度=X^2+Y=Z (X是双方间谍技术的差 Y是你发射间谍卫星数量)

Z>=1 获得目标资源情报

Z>=2 获得目标舰队情报

Z>=3 获得目标防御情报

Z>=5 获得目标建筑情报

Z>=7 获得目标技术情报

 

    防御间谍比率是说,你间谍人或被人间谍时,卫星被打下的机率,该机率跟目标的驻守舰队数量成正比,跟派出的间谍卫星数成正比,以及你跟对方的间谍技术差成正比,详细的公式还没有发现。

    小技巧:如果间谍技术实在相差太大,或是只探到资源,可以直接派一颗间谍卫星用「攻击」指令,就可以从战斗报告中知道他家的舰队、防御数量以及武器技术、防御盾技术、飞船装甲化技术的等级,并且可以从能量大约推出资源厂的等级,派间谍卫星因为他是最便宜也是最快的船舰;但是如果对方舰队太多,也有可能在第一回合就结束你的间谍卫星而没有数据。

    注意:间谍一个星球,就要有开战的准备,间谍可以看到一个人许多的数据、加上积分统计、舰队统计等,就可以评估一个人的战力、生产力是否比较强,因此在发送间谍前请自行评估。评估方式可以先用「搜索」来确定他的排名,并且用「统计」来确定他联盟的状况。

 

导弹舰(Bomber)

    导弹舰专门用来摧毁星球上的重型防御装置。拜雷射导引系统之赐,等离子炸弹被精确的投掷到它们的目标上,对行星的防御系统造成毁灭性的伤害。

    当超空间引擎提升到等级8时,导弹舰将可以换装成该型引擎并且提升速度。

快速射击对 间谍卫星: 5

快速射击对 太阳能卫星: 5

快速射击对 飞弹发射器: 20

快速射击对 轻型雷射炮: 20

快速射击对 重型雷射炮: 10

快速射击对 中子炮: 10

死星 对这种船舰的快速射击: 25

    终极制压用攻城兵器,对大部分炮台都有相当高的射速,但是其短航程,低速度及相当高的燃料费,令人诟病,所以目的偏向是制压玩家的防御用,而不是抢人。即使能换装引擎,航程还是受到相当限制。

    用来消灭防御的大家伙,是这玩意设计时的主要概念。但是并不真的是这样。不是说它没办法消灭防御建筑,而是因为它太慢,消耗太多重氢在飞行和建造上,也不适合用在掠夺战术,只有在压制玩家的时候才有用。你需要一大票的导弹舰才能消灭一整群的防御建筑,如果使用和那些防御总合造价相同的战列来攻击会比较划算。导弹舰也只对小型的防御有效(因为有快速射击),中子炮,导弹发射器,轻雷重雷。对抗高斯炮和离子炮的时候没有快速射击。

 

太阳能卫星(Solar Satellite)

    太阳能卫星装备了光伏电池,是构造简单的轨道卫星,它会把搜集到的能量传回到星球的地表上。这些能量使用特殊的雷射光来传输。 太阳能卫星的效率取决于太阳光线的强弱。

    它的价格低廉,安装简单,这些卫星因为提供了地面建筑所需的电力而广为人知。

    不幸的是,这些卫星在战斗时极易被摧毁。

 

小型运输机 对这种船舰的快速射击: 5

大型运输机 对这种船舰的快速射击: 5

轻型战斗机 对这种船舰的快速射击: 5

重型战斗机 对这种船舰的快速射击: 5

巡洋舰 对这种船舰的快速射击: 5

战列舰 对这种船舰的快速射击: 5

殖民飞船 对这种船舰的快速射击: 5

回收船 对这种船舰的快速射击: 5

导弹舰 对这种船舰的快速射击: 5

毁灭者 对这种船舰的快速射击: 5

死星 对这种船舰的快速射击: 1250

 

顾名思义,FleetSave就是舰队保护的意思,是新手保护自己的方法。因为新加入的玩家无法抵挡强大的舰队,所以在遭受攻击的时候,与其思考如何抵挡对方的进攻,不如思考如何保护自己的资源与舰队。这也是当你不在在线的时候,确保自己舰队不被别人摧毁的方法。

 

    一般情况下,当自己的星球遭受攻击时,星球上所有需要造船厂才能生产的东西(防御、舰艇、卫星)都会参与战斗。然而在强大的敌方舰队下,数量不足或弱小的舰种将无法避免被摧毁的命运。 FleetSave的第一个定义就是让这些脆弱的舰队在敌人到来前离开。因舰船离开星球后就会处于无法攻击的状态,除了被追秒外,绝对安全。

    此外,为了保护自己的资源(并且让敌人抢不到资源),要尽量将星球的资源装载于舰队上,随舰队离开,并且将原料生产效率调整为0%。

    让舰队离开常用的方法有下列几种:

     让「间谍卫星」跟随舰队前往无防御的「星球」,就可以选择「间谍」任务,如此一来舰队到达对方的星球后可以载着原有的资源返回。这个方法需要考虑到舰队可能被发现而与对方战斗的风险。

    让「回收船」跟随舰队前往「战场废墟」,就可以选择「回收」任务,如此一来舰队到达TF后可以载着原有的资源及「战场废墟」返回。

    让舰队前往到朋友的「星球」进行「运输」任务。

    让舰队前往自己的「星球」进行「运输」或「殖民」任务。

    让舰队前往前往无防御且久没上线的「星球」进行「攻击」,顺便抢一些资源回家。

    舰队出发时可以调整速率,控制往返的时间,当然你也可以让出发的舰队中途被叫回。速度越慢,「重氢」消耗越少。

    FleetSave除了作为被攻击时的应变措施,也可以作为不在在线时的预防措施。在这此情况下,资源产率不用调为0%,只要让舰队和资源在星际间移动即可。派出舰队的方法与上述方法相同,可藉由调整速率,让舰队在自己下一次上线时返回。

 

    平时都是自己抢别人,但是当有一天有个比你强的人来打你时怎么办?

    如果你在在线,看到有人来打你而你没有把握获胜,在他快到的时候, 做下列几步动作:

    首先把资源花在建筑和科技上,因为之后可以取消,资源会加回来。但是不要把钱存在造船厂或防御,因为这些不能取消建造。如果资源还有剩, 在他到达的前一两分钟把发电场电力关掉,使资源不再生产。然后让全部舰队带着星球上的全部资源逃离现场,这会让他抢到零资源。

    在他打完之后,把舰队叫回来,发电场打开,取消建筑和科技,可以把资源全部还原。 记得在派出舰队时选离你最近的「星球」或「战场废墟」,而且速度调到10%,这样可以省燃料。

 

    当你不在在线的时候,如何确保自己舰队不被别人摧毁呢?

     移动中的舰队不会被打。所以晚上睡觉或不在在线的时候,要让舰队离开星球。若舰队中有「回收船」,让你的舰队带着所有资源到附近的「战场废墟」,进行「回收」的任务。 若舰队中有「间谍卫星」,派舰队到被探测机率0% (通常是无防御的闲置星球) 进行「间谍」任务。 利用调整飞行速度让单程时间大约是你下线时间的一半,如此一来当你再次上线的时候,舰队刚好带着所有资源回来。