0%

首先要知道内网地址是不能直接和外网通信的。这里就牵涉到了一个NAT协议。现在用的最多的是NAT的一种NAPTNetwork Address/Port Translator的技术。       简单来说NAPT就是:当你与外网建立连接时,本地IP为192.168.0.11:4000,要与外网202.160.4.40:3300建立连接。NAT做的工作就是把本地地址映射成为外网地址(想连上internet都得至少有个外网IP)如外网IP为202.202.202.202,那么NAT就把它映射为202.202.202.202:5000(端口号随机的)。那么连接就变为202.202.202.202:5000到 202.160.4.40:3300。那么如果Server发送来的包就又被NAT逆向转换为内网IP。就可以通信了。         当然其他IP如果向202.202.202.202:5000发送信息是会被丢弃的,如何连接呢?       这就是内网与内网通信       这里需要一个Server,两方都登陆,Server记录下a,b双方的IP。a,b双方都能和Server通信,那么a,b怎么通信呢,这就需要神奇的打洞技术。a欲发信息与b,a把此事告知中间人S,S转告b,由于匿名的IP会被b丢弃,所以,要把a的地址变为不是匿名。所以b向a的地址发送信息(此为打洞),显然连不上,但是网关却记录下了a的地址,此时a再发信息到b就搞定了。反过来就能发信息给a,因此连接建立!a—>b? a->s s->b b->a? a->b ok!

通过内网与外网通信 内网与内网通信 NAT_noob_百度空间.

到香港玩,顺便买了个mac mini ,4688港币,比内地便宜个800块,加8g内存,买了个无线逻辑270鼠标套装 共530 ,入手一个24寸的三星显示器,1399

总价:5800

接下来准备做ios开发。

要学习的东西真多啊,看了好几本opengl es的书,html5游戏开发以及3D方便的基础教程。路漫漫其修远兮!

 

光盘引导————开机按住C键                                         U盘引导——–开机按住OPTION键(也是ALT键)

制作MAC OS X LION的U盘启动或光盘安装(收集整理)

1.下载LION。(3.74G)  注意:先不要安装LION,安装完后安装包会自动删除~ 如果你已经升级到了 Lion,只能重新下载的话,苹果会告诉你已经安装了系统,也就是说不能直接下载,解决这个问题的办法是在 Mac App Store 中按住 option 键点击安装,这样就可以跳过版本检查下载 Lion 安装程序。)—来自 applefans2008 大大,感谢

2. 右键你刚下载的LION—显示安装包内容—进入「SharedSupport」—可以看到「InstallESD.dmg」


3.另外再打开—-「应用程序」——「实用工具」——「磁盘工具」—把 InstallESD.dmg 拖到磁盘工具窗口左边的空白处


4.插入U盘  (最好5G以上) 或光盘(DVD)   以下就以U盘为例,光盘直接刻录,很简单


5.在 「磁盘工具」中 选中你的U盘—-点击分区标签—格式选择Mac OS 扩展(日志式)—点选项—–选择CUID 分区表—-好—-应用


6.分区完后~~ 点恢复标签—-把左边 的InstallESD.dmg 拖到 源磁盘那里,自动识别地址然后点恢复~~~~~~等待吧,颤抖吧~~

利用C++试用JSON第三方库JsonCpp - 51CTO.COM.

本文选择第三方库JsonCpp来解析json,JsonCpp是比较出名的c++解析库,在json官网也是首推的。

JSON是一个轻量级的数据定义格式,比起XML易学易用,而扩展功能不比XML差多少,用之进行数据交换是一个很好的选择

JSON的全称为:JavaScript Object Notation ,顾名思义,JSON是用于标记javascript对象的,详情参考http://www.json.org/。

本文选择第三方库JsonCpp来解析json,JsonCpp是比较出名的c++解析库,在json官网也是首推的。

JsonCpp简介

JsonCpp主要包含三种类型的class:Value Reader Writer。

jsoncpp中所有对象、类名都在namespace json中,包含json.h即可。

注意: Json::Value只能处理ANSI类型的字符串,如果C++程序使用Unicode编码的,最好加一个Adapt类来适配。

下载和编译

本文运行环境是: Redhat 5.5 + g++version 4.6.1 + GNU Make 3.81 + jsoncpp-0.5.0

下载地址是:http://sourceforge.net/projects/jsoncpp/

解压之后得到jsoncpp-src-0.5.0文件夹,我们只需要jsoncpp的头文件和cpp文件,其中jsonscpp的头文件位于jsoncpp-src-0.5.0includejson,jsoncpp的cpp文件位于jsoncpp-src-0.5.0srclib_json。

这里我列出我们的工作目录:

jsoncpp/ //工作目录

|-- include //头文件根目录

|  |-- json //json头文件,对应jsoncpp-src-0.5.0includejson

|-- src //cpp源码文件根目录

|-- json //jsoncpp源码文件,对应jsoncpp-src-0.5.0srclib_json

|-- main.cpp //我们的主函数,调用jsoncpp的示例代码

|-- makefile //makefile,不用我们多说了吧,不懂请看我博客的makefile最佳实践

反序列化Json对象

假设有一个json对象如下:

  1. {  
  2. "name": "json″,  
  3. "array": [  
  4. {  
  5. "cpp""jsoncpp" 
  6. },  
  7. {  
  8. "java""jsoninjava" 
  9. },  
  10. {  
  11.  "php""support" 
  12. }  
  13. ]  

我们要实现这个json的反序列号代码如下:

  1. voidreadJson() {  
  2. usingnamespacestd;  
  3. std::stringstrValue = "{\"name\":\"json\",\"array\":[{\"cpp\":\"jsoncpp\"},{\"java\":\"jsoninjava\"},{\"php\":\"support\"}]}";  
  4. Json::Reader reader;  
  5. Json::Value value;  
  6. if(reader.parse(strValue, value))  
  7. {  
  8. std::stringout= value["name"].asString();  
  9. std::cout <<out<<std::endl;  
  10. constJson::Value arrayObj = value["array"];  
  11.  for(unsigned inti = 0;i <arrayObj.size(); i++)  
  12. {  
  13. if(!arrayObj[i].isMember("cpp"))  
  14. continue;  
  15. out= arrayObj[i]["cpp"].asString();  
  16. std::cout <<out;  
  17. if(i != (arrayObj.size() - 1))  
  18. std::cout <<std::endl;  
  19.  }  
  20. }  

序列化Json对象

  1. voidwriteJson() {  
  2. usingnamespacestd;  
  3. Json::Value root;  
  4. Json::Value arrayObj;  
  5. Json::Value item;  
  6. item["cpp"] = "jsoncpp";  
  7. item["java"] = "jsoninjava";  
  8. item["php"] = "support";  
  9. arrayObj.append(item);  
  10. root["name"] = "json";  
  11. root["array"] = arrayObj;  
  12. root.toStyledString();  
  13. std::stringout= root.toStyledString();  
  14. std::cout <<out<<std::endl;  
  15.  } 

完整代码下载

点击此处下载

下载之后,执行以下命令

unzip jsoncpp.zip

cd jsoncpp

make

./main

原文:http://www.cnblogs.com/ggjucheng/archive/2012/01/03/2311107.html

OPhone 3D开发之射线拾取 - 技术文章 - OPhone SDN [OPhone 开发者网络].

 在前两篇介绍OPhone 3D开发的文章中,我们着重介绍了OPhone平台中强大的3D渲染API。在本文中,我们将通过编写一个触摸屏幕拾取3D空间图元的小程序,来向大家展示如何在OPhone中处理2D屏幕空间与3D世界的交互,并附带介绍包围体、几何相交检测等其他相关知识。程序最终的效果如图1所示,选中的三角形呈现红色半透明,深红色点表示当前屏幕触摸位置:

图1 三角形拾取程序效果图

什么是拾取

       在PC 3D游戏中,我们通常需要用鼠标来点击选中屏幕上某个模型。鼠标所在的屏幕是2D平面空间,而游戏世界则是标准的三维立体空间。这个根据2D屏幕坐标来选取3D空间中图元的操作,就是拾取。在手机平台中,触摸屏幕来代替了鼠标点击,三角形则是最基本的渲染图元。因此,如何通过触摸屏幕,来精确选中模型的某个三角形,正是本文所讨论的课题。
拾取射线
         在拾取操作中,首要的一步,就是根据屏幕触摸坐标,来得到3D空间中的拾取射线。有关计算拾取射线的数学推导,这里不做过多叙述,读者可以自行查阅相关资料,下面将直接介绍如何获得拾取射线。
图2 拾取原理图
       如图2所示,z = 0处为视锥体近剪裁面,z = 1处为远剪裁面。我们的拾取射线,就是由触摸位置在近剪裁面上的位置P0,以及在远剪裁面上的位置P1所组成的,其中,P0为射线原点,射线由P0发射指向P1。我们将从P0出发,沿着这条拾取射线,寻找到离P0最近的一个相交三角形,并将其渲染在屏幕上。因此,我们的问题就变成如何求P0以及P1的三维坐标。
      在求P0以及P1之前,我们首先要获得屏幕触摸事件的坐标。前面两篇文章中已经介绍过,在重载了GLSurfaceView的onTouchEvent()方法后,我们可以监听触屏事件,并得到事件发生的屏幕坐标(ScreenX,ScreenY)。这里需要注意的是,由于屏幕空间的原点位于左上角,而OpenGL中的视口坐标系中,原点处于左下角,因此,我们需要额外的一个操作,将屏幕坐标转化为OpenGL视口坐标:
OpenGLX = ScreenX;
OpenGLY = ViewportHeight – ScreenY;
        其中,ViewportHeight是指视口高度。
       在获得了OpenGL中的视口坐标之后,OPhone平台中提供了一个辅助函数GLU.gluUnproject(),用于将2D视口坐标,转换为3D空间坐标。该函数详细参数如下:
public static int gluUnProject (float winX, float winY, float winZ, float[] model, int modelOffset, float[] project, int projectOffset, int[] view, int viewOffset, float[] obj, int objOffset)
       可以看到,只需要传入视口坐标(winX,winY,winZ)、以及当前的模型视图矩阵model、投影矩阵project,便可得到3D空间中的坐标,并将计算结果存储到obj数组中返回。
       其中,winX就是ScreenX,winY就是OpenGLY,而winZ,我们在求P0时,winZ传入0,求P1时,winZ设置为1,计算得到的P0和P1的三维坐标,将存储在obj数组中。
       在得到P0和P1后,需要将这两个点转换为拾取射线。P0作为射线的原点,根据向量P1-P0来得到射线的方向,相关代码如下:
  1. /** 
  2.      * 更新拾取射线 
  3.      * @param screenX - 屏幕坐标X 
  4.      * @param screenY - 屏幕坐标Y 
  5.      */  
  6.     public static void update(float screenX, float screenY) {       AppConfig.gMatView.fillFloatArray(AppConfig.gpMatrixViewArray);  
  7.          
  8.        //由于OpenGL坐标系原点为左下角,而窗口坐标系原点为左上角  
  9.        //因此,在OpenGl中的Y应该需要用当前视口高度,减去窗口坐标Y  
  10.        float openglY = AppConfig.gpViewport[3] - screenY;  
  11.        //z = 0 , 得到P0  
  12.        gProjector.gluUnProject(screenX, openglY, 0.0f, AppConfig.gpMatrixViewArray, 0,   
  13.               AppConfig.gpMatrixProjectArray, 0, AppConfig.gpViewport, 0, gpObjPosArray, 0);  
  14.        //填充射线原点P0  
  15.        gPickRay.mvOrigin.set(gpObjPosArray[0], gpObjPosArray[1], gpObjPosArray[2]);  
  16.          
  17.        //z = 1 ,得到P1  
  18.        gProjector.gluUnProject(screenX, openglY, 1.0f, AppConfig.gpMatrixViewArray, 0,   
  19.               AppConfig.gpMatrixProjectArray, 0, AppConfig.gpViewport, 0, gpObjPosArray, 0);  
  20.        //计算射线的方向,P1 - P0  
  21.        gPickRay.mvDirection.set(gpObjPosArray[0], gpObjPosArray[1], gpObjPosArray[2]);  
  22.        gPickRay.mvDirection.sub(gPickRay.mvOrigin);  
  23.        //向量归一化  
  24.        gPickRay.mvDirection.normalize();  
  25.     }  
      这样,只需要在触屏事件触发时,我们从外部调用这个update(int,int)方法,将最新触屏的屏幕坐标传入,就可以更新拾取射线。
矩阵托管
现在还有另一个问题需要解决,就是如何得到投影矩阵,以及当前的模型视图矩阵呢?在桌面版的OpenGL中,我们可以通过查询函数glGetFloatv(matrixMode, mat)来得到当前的模型视图矩阵或者投影矩阵,但在OpenGL ES平台中,该函数被精简了,我们无法直接查询得到底层管线的这些矩阵信息,因此只有通过在外部自行维护相应的矩阵拷贝来实现随时的矩阵访问。在这里,我们不得不避开了OPhone平台中提供给我们的简单易用的GLU. gluPersective()函数以及GLU.gluLookAt()函数,而自己去实现相同功能的函数,传入同样的参数,但将计算结果存储在矩阵中保存并返回。相关代码如下:
  1. /** 
  2.      * 模拟实现GLU.gluLookAt()函数,参数相同,将计算结果矩阵返回 
  3.      * @param eye 
  4.      * @param center 
  5.      * @param up 
  6.      * @param out - 返回的计算结果矩阵 
  7.      */  
  8.     public static void gluLookAt(Vector3f eye, Vector3f center, Vector3f up, Matrix4f out) {  
  9.     tmpF.x = center.x - eye.x;  
  10.     tmpF.y = center.y - eye.y;  
  11.     tmpF.z = center.z - eye.z;  
  12.       
  13.     tmpF.normalize();  
  14.     tmpUp.set(up);  
  15.     tmpUp.normalize();  
  16.       
  17.     tmpS.cross(tmpF, tmpUp);  
  18.     tmpT.cross(tmpS, tmpF);  
  19.       
  20.     out.m00 = tmpS.x;  
  21.     out.m10 = tmpT.x;  
  22.     out.m20 = -tmpF.x;  
  23.     out.m30 = 0;  
  24.       
  25.     out.m01 = tmpS.y;  
  26.     out.m11 = tmpT.y;  
  27.     out.m21 = -tmpF.y;  
  28.     out.m31 = 0;  
  29.       
  30.     out.m02 = tmpS.z;  
  31.     out.m12 = tmpT.z;  
  32.     out.m22 = -tmpF.z;  
  33.     out.m32 = 0;  
  34.       
  35.     out.m03 = 0;  
  36.     out.m13 = 0;  
  37.     out.m23 = 0;  
  38.     out.m33 = 1;  
  39.       
  40.     tmpMat.setIdentity();  
  41.     tmpMat.setTranslation(-eye.x, -eye.y, -eye.z);  
  42.       
  43.     out.mul(tmpMat);  
  44.     }  
  45.     /** 
  46.      * 模拟实现GLU.gluPersective()函数,参数相同,将计算结果填入返回矩阵中 
  47.      * @param fovy 
  48.      * @param aspect 
  49.      * @param zNear 
  50.      * @param zFar 
  51.      * @param out - 计算结果返回 
  52.      */  
  53.     public static void gluPersective(float fovy, float aspect, float zNear, float zFar, Matrix4f out) {  
  54.     float sine, cotangent, deltaZ;  
  55.     float radians = (float)(fovy / 2 * Math.PI / 180);  
  56.    
  57.         deltaZ = zFar - zNear;  
  58.         sine = (float)Math.sin(radians);  
  59.    
  60.         if ((deltaZ == 0) || (sine == 0) || (aspect == 0)) {  
  61.           return;  
  62.         }  
  63.    
  64.         cotangent = (float)Math.cos(radians) / sine;  
  65.    
  66.         out.setIdentity();  
  67.    
  68.         out.m00 = cotangent / aspect;  
  69.         out.m11 = cotangent;  
  70.         out.m22 = - (zFar + zNear) / deltaZ;  
  71.         out.m32 = -1;  
  72.         out.m23 = -2 * zNear * zFar / deltaZ;  
  73.         out.m33 = 0;  
  74.     }  
     这样,我们在设置投影矩阵时,将变成为下面的方式:
  1. //设置投影矩阵  
  2.        float ratio = (float) width / height;//屏幕宽高比  
  3.        gl.glMatrixMode(GL10.GL_PROJECTION);  
  4.        gl.glLoadIdentity();  
  5.        //GLU.gluPerspective(gl, 45.0f, ratio, 1, 5000);系统方式  
  6.        Matrix4f.gluPersective(45.0f, ratio, 15000, AppConfig.gMatProject);  
  7.     gl.glLoadMatrixf(AppConfig.gMatProject.asFloatBuffer());  
     设置视图矩阵时就变成:
  1. //设置视图矩阵  
  2.        gl.glMatrixMode(GL10.GL_MODELVIEW);  
  3.        gl.glLoadIdentity();  
  4.        //GLU.gluLookAt(gl, mfEyeX, mfEyeY, mfEyeZ, mfCenterX, mfCenterY, mfCenterZ, 0, 1, 0);//系统方式  
  5.        Matrix4f.gluLookAt(mvEye, mvCenter, mvUp, AppConfig.gMatView);  
  6.     gl.glLoadMatrixf(AppConfig.gMatView.asFloatBuffer());  
      这样,gMatProject以及gMatView,就是我们实时的投影矩阵和视图矩阵。在调用GLU.gluUnProject()函数时,将这两个矩阵对象以列优先的顺序写入到一个float[16]的矩阵数组中使用即可。
射线相交检测
       现在我们得到了3D空间中的拾取射线,就可以通过射线与模型的相交检测,来判定所要拾取的模型了。在本例中,我们使用的模型格式依然是前面所介绍的MS3D格式。在MS3D模型中,每个模型有若干个分组(Group),每个分组是由若干个三角形构成的。因此,我们需要把射线与每一个三角形进行相交检测。由于我们这里的需求,是仅仅得到射线从原点发射之后的前进路径上,第一个与之相交的三角形。因此,可以通过比较射线原点与相交点的线性距离,得到那个距离最近的,这个三角形就是我们所需要的。相关代码如下:
  1. /** 
  2.      * 射线与模型的精确碰撞检测 
  3.      * @param ray - 转换到模型空间中的射线 
  4.      * @param trianglePosOut - 返回的拾取后的三角形顶点位置 
  5.      * @return 如果相交,返回true 
  6.      */  
  7.     public boolean intersect(Ray ray, Vector3f[] trianglePosOut) {  
  8.        boolean bFound = false;  
  9.        //存储着射线原点与三角形相交点的距离  
  10.        //我们最后仅仅保留距离最近的那一个  
  11.        float closeDis = 0.0f;  
  12.          
  13.        for(int i = 0; i < mpGroups.length; i++) {  
  14.            //遍历每个Group  
  15.            mpBufVertices[i].position(0);  
  16.            int vertexCount = mpBufVertices[i].limit() / 3;  
  17.            int triangleCount = vertexCount / 3;  
  18.            //由于我们提交渲染的顶点数据是以三角形列表的形式填充的,不牵扯到索引值  
  19.            //因此,每3个顶点就组成一个三角形  
  20.            for(int idxTriangle = 0; idxTriangle < triangleCount; idxTriangle++) {  
  21.               //遍历每个三角形  
  22.               //填充三角形数据,顶点v0, v1, v2  
  23.               IBufferFactory.read(mpBufVertices[i], v0);  
  24.               IBufferFactory.read(mpBufVertices[i], v1);  
  25.               IBufferFactory.read(mpBufVertices[i], v2);  
  26.               //进行射线和三角行的碰撞检测  
  27.                if(ray.intersectTriangle(v0, v1, v2, location)) {  
  28.                   //如果发生了相交  
  29.                   if(!bFound) {  
  30.                      //如果是初次检测到,需要存储射线原点与三角形交点的距离值  
  31.                      bFound = true;  
  32.                      closeDis = location.w;  
  33.                      trianglePosOut[0].set(v0);  
  34.                      trianglePosOut[1].set(v1);  
  35.                      trianglePosOut[2].set(v2);  
  36.                   } else {  
  37.                      //如果之前已经检测到相交事件,则需要把新相交点与之前的相交数据相比较  
  38.                      //最终保留离射线原点更近的  
  39.                      if(closeDis > location.w) {  
  40.                          closeDis = location.w;  
  41.                          trianglePosOut[0].set(v0);  
  42.                          trianglePosOut[1].set(v1);  
  43.                          trianglePosOut[2].set(v2);  
  44.                      }  
  45.                   }  
  46.               }  
  47.            }  
  48.            //重置Buffer  
  49.            mpBufVertices[i].position(0);  
  50.        }  
  51.          
  52.        return bFound;  
  53.     }  
        MS3D的渲染提交数据是以三角形列表的形式填充的,不牵扯到外部索引。因此我们以每3个顶点为单位构建一个三角形,来与射线进行相交检测。这就面临一个问题,我们所使用的顶点数据,是处于模型坐标系中的模型内部顶点,也就是把模型坐标系的原点放置与世界坐标系中的原点,并且对模型不加以任何的平移旋转缩放操作,这时模型坐标系中的顶点位置就是世界坐标系中的顶点位置。而一旦模型进行了平移、旋转或者缩放操作之后,模型坐标系和世界坐标系就不再相同,对于模型的内部顶点,需要经过模型矩阵的变换,才能得到世界坐标系中的位置。而前面我们得到的拾取射线,是在世界坐标系中的表示。因此,我们需要将模型顶点与射线统一到一个坐标系中。这里有两种方式,一种是把全部顶点都转换到世界坐标系中,与拾取射线相检测;另一种就是把拾取射线变换到模型坐标系中。从计算量上来看,模型可能有数百个顶点需要变换,而射线变换仅仅需要2次顶点变换。无疑,我们会选择后一种方法。
          在之前的例子中,我们对于模型矩阵的操作,也是调用的系统变换函数,glTranslate()、glRotate()、glScale()等。但这也面临同样的一个问题,就是无法得到管线底层的当前模型矩阵。因此,我们必须要托管模型矩阵变换信息,这样,操作模型的代码就改变成为:
  1. //-------使用系统函数进行变换  
  2.     //gl.glRotatef(mfAngleX, 1, 0, 0);//绕X轴旋转  
  3.     //gl.glRotatef(mfAngleY, 0, 1, 0);//绕Y轴旋转  
  4.     //-------托管方式进行变换  
  5. Matrix4f matRotX = new Matrix4f();  
  6.     Matrix4f matRotY = new Matrix4f();  
  7.     matRotX.setIdentity();  
  8.     matRotY.setIdentity();  
  9.     matRotX.rotX((float)(mfAngleX * Math.PI / 180));  
  10.     matRotY.rotY((float)(mfAngleY * Math.PI / 180));  
  11.          
  12.     AppConfig.gMatModel.set(matRotX);         AppConfig.gMatModel.mul(matRotY);  
  13.              
  14. gl.glMultMatrixf(AppConfig.gMatModel.asFloatBuffer());  
     经过上面的操作,我们的模型矩阵就存储在gMatModel中,并可以随时访问。
      
      模型矩阵的作用,是将模型坐标系中的顶点,变换到世界坐标系中。而我们的需求是把射线从世界坐标系变换到模型坐标系中。因此,我们需要首先得到模型矩阵的逆矩阵,然后用这个逆矩阵去变换射线,变换后的结果就是射线在模型坐标系中的表示。从而可以用来与模型坐标系中的三角形进行相交检测。变换射线的相关代码如下:
  1. /** 
  2.      * 变换射线,将结果存储到out中 
  3.      * @param matrix - 变换矩阵 
  4.      * @param out - 变换后的射线 
  5.      */  
  6.     public void transform(Matrix4f matrix, Ray out) {  
  7.        Vector3f v0 = Vector3f.TEMP;  
  8.        Vector3f v1 = Vector3f.TEMP1;  
  9.        v0.set(mvOrigin);  
  10.        v1.set(mvOrigin);  
  11.        v1.add(mvDirection);  
  12.    
  13.        matrix.transform(v0, v0);  
  14.        matrix.transform(v1, v1);  
  15.    
  16.        out.mvOrigin.set(v0);  
  17.        v1.sub(v0);  
  18.        v1.normalize();  
  19.        out.mvDirection.set(v1);  
  20.     }  
包围体快速排除
       在模型与射线相交检测的方法中,大家可以看 到,我们需要把模型的每一个三角形与射线进行精确的相交检测。一个模型往往有数百个面,这样一来,精确检测对于CPU来说就成了一项繁重的工作。因此,我们需要尽可能的少调用这个方法。假设模型位于屏幕的中央,在点击屏幕边角时,很明显是无法拾取模型的,这时候,就需要我们使用一种快速排除策略,来将大部分无效的拾取操作过滤掉,因此我们引入了包围体的概念。所谓包围体,就是将一组物体完全包容起来的封闭空间。将复杂模型用简单的包围体封装起来,可以大大提高几何运算的效率。
图3 常用包围体 a)包围球 b)轴对称包围盒 c)定向包围盒
        如图3所示,常用的包围体有包围球(Sphere),轴对称包围盒(AABB),定向包围盒(OBB)等。此外,还有椭圆、圆柱、胶囊、凸包等其他包围体。从上图可以看到,对于不同形状的模型,不同包围体的空间冗余度会有很大差异。在实际应用中,根据具体情况来选择一个最合适的包围体,往往可以达到事半功倍的效果。本例中,我们采用的是最简单的包围球。包围球与射线相交检测的代码非常简单,速度也非常快:
  1. /** 
  2.      * 检测射线是否与包围球相交 
  3.      *  
  4.      * @param center 
  5.      *            圆心 
  6.      * @param radius 
  7.      *            半径 
  8.      * @return 如果相交返回true 
  9.      */  
  10.     public boolean intersectSphere(Vector3f center, float radius) {  
  11.        Vector3f diff = tmp0;  
  12.        diff.sub(mvOrigin, center);  
  13.        float r2 = radius * radius;  
  14.        float a = diff.dot(diff) - r2;  
  15.        if (a <= 0.0f) {  
  16.            //在包围球内  
  17.            return true;  
  18.        }  
  19.          
  20.        float b = mvDirection.dot(diff);  
  21.        if (b >= 0.0f) {  
  22.            return false;  
  23.        }  
  24.        return b * b >= a;  
  25.     }  
      载入模型时,我们会根据模型所有的顶点,构建出模型的包围球。注意这个初始的包围球也是基于模型坐标系中的表示,因此在与射线进行相交检测时,也要注意两者应处于同一个坐标系中。
      
        有了包围球之后,我们的拾取判定流程,就增加了一步。先将拾取射线与模型的包围球做快速的相交检测,如果两者不相交,那么就无须进行下一步的最为耗时的射线与模型的精确三角形相交检测。完整代码如下:
  1. /** 
  2.      * 更新拾取事件 
  3.      */  
  4.     private void updatePick() {  
  5.        if(!AppConfig.gbNeedPick) {  
  6.            return;  
  7.        }  
  8.        AppConfig.gbNeedPick = false;  
  9.        //更新最新的拾取射线  
  10.        PickFactory.update(AppConfig.gScreenX, AppConfig.gScreenY);  
  11.        //获得最新的拾取射线  
  12.        Ray ray = PickFactory.getPickRay();  
  13.          
  14.        //首先把模型的绑定球通过模型矩阵,由模型局部空间变换到世界空间  
  15.        AppConfig.gMatModel.transform(mModel.getSphereCenter(), transformedSphereCenter);  
  16.        //首先检测拾取射线是否与模型绑定球发生相交  
  17.        //这个检测很快,可以快速排除不必要的精确相交检测  
  18.        if(ray.intersectSphere(transformedSphereCenter, mModel.getSphereRadius())) {  
  19.            //如果射线与绑定球发生相交,那么就需要进行精确的三角面级别的相交检测  
  20.            //由于我们的模型渲染数据,均是在模型局部坐标系中  
  21.            //而拾取射线是在世界坐标系中  
  22.            //因此需要把射线转换到模型坐标系中  
  23.            //这里首先计算模型矩阵的逆矩阵  
  24.            matInvertModel.set(AppConfig.gMatModel);  
  25.            matInvertModel.invert();  
  26.            //把射线变换到模型坐标系中,把结果存储到transformedRay中  
  27.            ray.transform(matInvertModel, transformedRay);  
  28.            //将变换后的射线与模型做精确相交检测  
  29.            if(mModel.intersect(transformedRay, mpTriangle)) {  
  30.               //如果找到了相交的最近的三角形  
  31.               AppConfig.gbTrianglePicked = true;  
  32.               //填充数据到被选取三角形的渲染缓存中  
  33.               mBufPickedTriangle.position(0);  
  34.               for(int i = 0; i < 3; i++) {  
  35.                   IBufferFactory.fillBuffer(mBufPickedTriangle, mpTriangle[i]);  
  36.               }  
  37.               mBufPickedTriangle.position(0);  
  38.            }  
  39.        } else {  
  40.            AppConfig.gbTrianglePicked = false;  
  41.        }  
  42.     }  
渲染拾取的三角形
        在经历了上面一系列的操作之后,如果我们得到了拾取判定相交的三角形,则需要将其渲染出来。在本例中,我们将返回的三角形,以红色半透明纯色填充的方式渲染在模型之上。需要注意的是,我们得到的三角形数据也是模型坐标系中的位置,需要经过与该模型同样的模型变换后,将它们变换到世界坐标系中,才能使三角形与模型位置表现相一致。相关代码如下:
  1. /** 
  2.      * 渲染选中的三角形 
  3.      * @param gl 
  4.      */  
  5.     private void drawPickedTriangle(GL10 gl) {  
  6.        if(!AppConfig.gbTrianglePicked) {  
  7.            return;  
  8.        }  
  9.        //由于返回的拾取三角形数据是出于模型坐标系中  
  10.        //因此需要经过模型变换,将它们变换到世界坐标系中进行渲染  
  11.        //设置模型变换矩阵  
  12.        gl.glMultMatrixf(AppConfig.gMatModel.asFloatBuffer());  
  13.        //设置三角形颜色,alpha为0.7  
  14.        gl.glColor4f(1.0f, 0.0f, 0.0f, 0.7f);  
  15.        //开启Blend混合模式  
  16.        gl.glEnable(GL10.GL_BLEND);  
  17.        gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);  
  18.        //禁用无关属性,仅仅使用纯色填充  
  19.        gl.glDisable(GL10.GL_DEPTH_TEST);  
  20.        gl.glDisable(GL10.GL_TEXTURE_2D);  
  21.        //开始绑定渲染顶点数据  
  22.        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);  
  23.        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mBufPickedTriangle);  
  24.        //提交渲染  
  25.        gl.glDrawArrays(GL10.GL_TRIANGLES, 03);  
  26.        //重置相关属性  
  27.        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);  
  28.        gl.glEnable(GL10.GL_DEPTH_TEST);  
  29.        gl.glDisable(GL10.GL_BLEND);  
  30.     }  
高级话题
         在上面的讨论中,最为耗时的操作无疑是射线与模型所有三角形的相交检测。本例中的模型面数均较少,因此代价尚可接受。但如果模型有成千上万面,遍历检测将会极大占用CPU资源,尤其是在实时操作中会导致应用程序响应极为缓慢,这显然是让人无法接受的。解决此问题的方法,目前常用的是建立树形层次包围体,比如Sphere-tree、AABB-tree、OBB-tree、kD-tree等。由于层次包围体树的构建及使用比较复杂,限于篇幅关系,这里不做过多讨论,读者可自行查阅相关资料,或者可以通过EMail(xueyong@live.com)来与我一起讨论。
总结
      在开发过程中,应用程序与用户的交互是非常重要的一部分,而在3D中,这种交互处理起来相对复杂一点。本文通过分析和解决手机触摸屏幕拾取3D空间图元的问题,向读者介绍了OPhone平台中如何处理2D与3D的交互,以及包围体、射线和几何相交检测等相关知识。
作者介绍
        薛永,专注于移动平台3D应用程序的开发,熟悉M3G,JSR 239,OpenGL ES(OPhone&iphone)等多种移动3D开发平台。目前正在自主开发全套3D引擎,包括PC端场景/模型/动画/UI编辑器,3ds max导出插件,面向Java、C++的客户端。同时在制作一款3D射击游戏,到时会面向OPhone、iphone等多个平台发布。

对游戏和3D一直笔记感兴趣,希望有一天能够开发自己的一款风靡世界的游戏,android和ios平台的出现,有可能把这个希望变成现实,不过在开发出一款真正的游戏之前,得知道怎么编游戏。

找了很多中文资料,都是一些很简单的介绍,后来看到一本英文的ios游戏编程,才了解游戏是这样编写的,为了记录一下心的,准备写一些这方面的教程,也算是给喜欢编游戏的人做个教程。

游戏有很多的引擎,对于初学者,门槛太高,还是先简单的开始,理解了原理,再了解引擎也不迟,有能力自己开发引擎也可。

就我所知道的一些3D opengl es引擎:

android: mini3d andengine libgdx等等,mini3d够简单,可以看看

ios:cocos2d-x

&nbsp;

&nbsp;

&nbsp;

看到kinetc技术,了解了一些,我觉得对于游戏开发绝对是一个划时代的改变。

这个是一个完全不同的操作模式,完全不同的体验,

再结合各种手机,平板,以及各操作系统平台,会产生许多创新的技术。

可以设想:

1 有一款游戏,可以在电视上玩,通过kinect进行控制,也可以通过iphone ,android

手机进行远程操作,可以运行在mac os 或windows 。

最理想的状态,有硬件开发商开发出独立的可以通过无线连接的kinect ,

所有的平台,mac os ,ios ,android ,windows ,windows phone 等都可以

通过无线调用kinect识别出来的数据,通过在本地,或无线传输到大屏幕,玩各种游戏。

现有的游戏只要增加kinect识别做相应处理,就可以解决游戏不足问题。

&nbsp;

2 目前可以做的也有很多创新,娱乐,教育,学习都很很多空白领域等待开发。

——后续

前几天参加csdn的全球软件营销大会,看到一个很酷的设备,google开发的,一个类似中枢的硬件和,通过这个东西,可以无线或蓝牙进行NFC(近距离连接)传输,csdn蒋涛讲了一个玩具和语音结合的互动例子,以后的玩具有更多的智能,互动。

Soft is eating world !

软件正在蚕食着世界,大部分的东西都会软件化,或者软硬结合。

书本的电子化趋势也越来越明显,音乐,电影都数字化,

交互的技术发展,越来越多的创新会出现。

我设想的场景,也行过不了几年就会有:

1 家里有一台设备,移动的设备,家用电器都可以连接在一起,手机的屏幕可以无线传输到电视的大屏幕上。移动的设备上的游戏可以在电视上玩,借助类似kinect技术,完全解放出来,可以玩游戏的同时健身。

2 3D虚拟技术的成熟,通过佩戴3D眼镜,通过无线连接,完全可以没有显示设备,自己在3D眼镜显示,或显示在电视大屏幕上,结合kinect技术,就像漫游在真正的3D世界中,可以约朋友一起在虚拟的3D世界中一起跑步,玩游戏。现在的CS游戏,如果穿一身有反馈的衣服,AK手枪等设备能够反馈信息到系统中,那么这个游戏一定会很吸引人 。也许将来某一天,有一种体验馆,可以利用这种最先进的3D体感技术,实现虚拟3D时间的无缝融合。

&nbsp;

&nbsp;

有朋友咨询开发多人在线聊天系统,比较了一些受访和开源的系统,归纳如下:

1 服务端选择smrtfoxserver 2.x  :服务端开发可以选择的java 或python ,python可以快速的开发原型demo ,java也是比较好的选择,有很多的开源代码可用。客户端支持android ,actionscript ,unity 3d ,silverlight  等等,基本说对于移动开发可以包含。

有现成的成功案例,大公司也采用,不用担心性能问题,唯一缺点,费用太贵,免费可用100个用户。

2 服务端采用openfire 开源服务器:服务端采用java开发,内部使用mina 实现网络接口,mina也是和netty xsocket类似的无阻塞网络协议实现。客户端目前只能用android ,使用asmack库。采用xmpp协议实现消息流 。

3 如果不考虑手机端,可以采用node.js +websocket 实现,这个也是比较好。

4 可以考虑采用tornado python web服务器,这个也是无阻塞,支持1K以上用户连接。

5 建议采用amazon的ec服务器,以后扩展方便 ,前段nginx + tornado ,采用python 开发,客户端技术使用html5 +本地调用,平台:web+android +ios+wp7 ,使用rest接口进行数据交换 ,可用json格式,数据库:amazon的mysql 。

6 采用google gae + cloud sql ,优点:不必考虑服务扩展,google帮你做好了。

缺点: 限定了开发语言和环境。

本人没用过,接下来准备使用该5方案做一个demo :

目标:搭建一个以图片交流的平台,用户可用方便的手写后和其他用户交流,能同事容纳100K用户

周期: 1年 业余开发

&nbsp;

&nbsp;

&nbsp;