九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

折腾一晚上Bullet及Ogre相关工具的成果 -- 3Ds Max,Maya, blender, GameKit

起始目的很简单,整合Bullet及Ogre,找个能够生成.scene和.bullet文件的建模工具。

折腾一晚上Bullet及Ogre相关的东西,基本上就像爱迪生发明灯泡一样,得出了N个失败的教训,总结如下,大家不要再走弯路了。

 

1. Blender, 开源产品,我寄予了厚望,结果却是大大的失望,Blender的Ogre插件那个弱。。。。。Mesh导出还算可用,但是不能一次导出多个,要导出多个你就去吐血吧。而Scene导出插件简直就是跟你开国际玩笑,只能导出scene文件,需要你自己用Mesh插件导出所有的模型,于是乎,两者合作的结果是,你需要一个一个的导出mesh,然后再用Scene导出插件导出Scene。(不过经测试,的确可用)这个就算了,Blender内置bullet物理支持,编辑非常方便,在自己的Game Engine中模拟也是几乎完美,还支持constraints,我都惊叹了。。。。结果是,无法导出.bullet文件,用了erwin提供的改版blender,发现没有菜单,于是乎,google出了下列脚本:

import PhysicsConstraints
PhysicsConstraints.exportBullet("testFile.bullet")

运行之,发现没有PhysicsConstraints这个模块,再Google之,要先运行Game Engine(相当于动态加载了此模块),然后再运行脚本。(先按‘p'运行,然后按space键运行脚本)

好不容易出来个bullet文件,尝试加载之,一堆错误输出,有些物体被创建,但是可能因为坐标轴的问题导致重力方向不对,有点乱。。。。。。。。。。。

 

暂时放弃寄希望于Blender的mesh,scene导出(这个过程是在复杂)+.bullet文件导出了,那么用Blender自己的打包文件.blender吧,开始尝试GameKit。

 

2. GameKit,虽然以前试过,知道其还不成熟,到了现在感觉要用blender,那么就必须要用gamekit了(如上原因),这次硬着头皮实验,获取svn代码,直接用cmake生成iPhone工程(按官方文档方法所述),无法用Xcode打开,使用Gamekit工程中提供的改版cmake源码编译一套cmake,再生成工程,还是无法打开,下载官方发布的源代码,再生成,可以打开了(明显svn上最新的源码的cmake脚本有bug了)。编译,错误无数,查看到某人的类似经历,他竟然编译成功并且运行了,但是运行后错误同样惨不忍睹。遂放弃。。。。。

 

3. MAYA, OgreMax插件的MAYA版本同样异常出色,而Maya的bullet插件原来是迪斯尼做的,也还算好,支持简单的constraints。尝试载入了一个复杂的场景,导出bullet,结果载入时崩溃。自己尝试制作一个简单的场景,通过bullet的demo载入正常。也就是说估计哪里还是有问题,但是起码还算可用吧。然后尝试将此用Bullet插件建立的Max文件导出为scene,用Ogre载入,没有任何东西显示。。。。。这就悲剧了,同样是在MAYA当中,你要么使用Bullet的插件编辑Bullet的东西,可以到处.Bullet文件,但是无法显示,反之,用MAYA自身编辑的模型又无法拥有物理,无法导出.bullet文件,这是最大的悲剧,拆开可用,合起来不能用。。。。。、

 

 

4. 3Ds Max,
我最熟悉的工具,用于建模很好很强大,OgreMax这个Ogre的Scene导出也非常不错(除了愚蠢的不知道复制出来的模型可以用同一个mesh),
可以一次导出一个场景的所有模型的mesh文件及scene文件。最开始用Bullet的Max插件导出了一个.bullet文件无法使用,非常无奈。以为这个插件根本无法使用,差点放弃。但是因为实在没有太好的其他解决方案,所以后来自己通过插件源代码编译插件,希望新版插件能够修复一些bug,最后发现,问题的核心在于我通过plane创建出来的static的物体无法使用,(应该是转成bullet中的triangle mesh了,载入时会失败,但是其他的sphere和box等其实可以正常使用,包括普通的static物体。一下子让我在黑暗中看到了光明。。。。。。。。神啊。。。。。。。。。。。。。

 

但是其实还有几个问题,其一,物理和显示的关联。其二,bullet的坐标轴和Max的坐标轴不一样,导出的物理会有点混乱。但是,已经有路可以走了。。。。。。。。。。。

阅读全文....

Bullet物理引擎不完全指南(Bullet Physics Engine not complete Guide)

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

前言

    Bullet据称为游戏世界占有率为第三的物理引擎,也是前几大引擎目前唯一能够找到的支持iPhone,开源,免费(Zlib协议,非常自由,且商业免费)的物理引擎,但是文档资料并不是很好,Demo虽然多,但是主要出于特性测试/展示的目的,会让初学者无从看起,一头雾水。我刚学习Bullet的时候困于没有好的文档及资料,非常没有头绪,折腾了很久,所以就发挥没有就创造的精神,写作及整理此文,(以整理资料为主,自己写 为辅)希望大家在学习Bullet的时候不要再像我开始一样没有头绪。因为我实在没有精力去完成一个包含Bullet方方面面的完全指南,所以本文只能是不完全版本,这个就请大家谅解了,但是期望能够真正的完成一个简单的由浅入深的教程,并提供尽量详尽的额外信息链接,只能说让初学者比看官方的WIKI和Demo效果更好,大家有好的信息和资料而本文没有包含的,也请告诉我,我可以在新版中添加进来。因为我学习Bullet的时间也比较短,有不对的地方请高人指点。

    前段时间简单的学习了一下Bullet,牵涉到图形部分的时候主要都是研究Bullet与Ogre的结合,所以使用了OgreBullet这个Ogre的Addon,其实真正的学习当然还是直接利用Bullet本身附带的简单的debug OpenGL绘制就好了。本文就完全以Bullet本身的Debug功能来学习,虽然简陋,但是可以排除干扰,专注于bullet。也许除了本文,会有个额外的文章,稍微研究下Ogre与Bullet的整合和分析一下OgreBullet的源码。

   

Bullet介绍

    Bullet的主页
。最新版本在这里下载
。简单的中文介绍见百度百科
。一些也许可以促使你选择Bullet的小故事在以前的文章中有提及,参考这里
的开头--为什么选择Bullet。很遗憾的是前几天看到的一篇很详细的bullet中文介绍找不到了,将来也许补上。

安装

    Bullet作为一款开源物理引擎,你可以选择作者编译好的SDK
,或者直接从源码编译自己的版本(Windows版本自带VS工程)。得益于CMake,在其他平台从源码自己编译也非常简单,参考这里
。iPhone版本的话参考这里
。想要更详细点的图文教程可以参考Creating_a_project_from_scratch

Hello World Application

    在学习之前,没有接触过物理引擎的可以参考一下这个术语表

    这里
有个较为详细的教程。也包含在Bullet本身的一个名叫 AppHelloWorld 的Demo中。(注释也很详细,但是和WIKI上的版本略有不同)可以大概的对Bullet有个感觉。

    其实Bullet与Ogre走的一条路线,为了灵活,增加了很多使用的复杂性。(真怀念Box2D和Irrlicht的简单啊)其实即使希望通过strategy模式来增加灵活度,让用户可以自由的选择各类算法和解决方案,但是我还是感觉首先提供默认解决方案,用户需要不同方案的时候通过Set方式改变(甚至也可以new的时候修改)但是大牛们研究这些东西那么透,总是会觉得这个世界上不存在默认方案。。。。。因为没有方案是最优的,是适合大多数情况的,所以导致Bullet的HelloWorld程序源代码都已经超过100行。。。。。。。。。。-_-!发了点牢骚。。。。。

    通过HelloWorld程序,我们大概可以知道一些东西,比如建立一个Bullet物理世界的步骤,比如Bullet的类以bt(变态-_-!)开头,比如Bullet与Box2D这样的2D物理引擎一样,专注于数据的计算,本身没有图形输出,比如创建一个物理实体的时候也有shape的概念,然后通过一个结构作为参数(BodyConstructionInfo)来创建真实的物体,大概的熟悉一下就好,具体的细节还不懂,没有关系,一步一步来。

    另外,建议趁这个机会,确定自己机器使用Bullet的环境,特别是Win32下,我的使用方法是,利用BULLET_HOME环境变量指明Bullet安装的位置,BULLTE_LIBS指明最后编译完的静态库的位置,工程中利用这两个环境变量来确定位置。(这种用法很适合屏蔽各机器的环境不同)最后的Hello World工程见https://bullet-sample.jtianling.googlecode.com/hg/中的Bullet-HelloWorld。

    请确保该Hello World程序能够运行(无论是你自己的还是用我的)然后才继续下面的内容。

让你坐在司机的位置上

    该怎么学习的问题,向来都是各执一词,有人认为该从最基础的学起,就像建房子一样打好地基,有人会更加推崇自上而下的学习(Top-Down Approach),我属于后一派,能先写有用的可以摸到的程序,然后一层一层的向下学习,这样会更加有趣味性,并且学习曲线也会更加平缓,假如你是前一派,那么推荐你先看完Bullet的User Manual,然后是Bullet所有的Tutorial Articles
,然后再自己一个一个看Demo。

    在Hello World的例子中你已经可以看到文本数据的输出,能够看到球/Box的落下了,但是很明显太不直观了,得益于Bullet良好的debug输出支持,我们要先能直观的通过图形看到球的落下!先坐在司机的位置上才能学会开车^^你也不至于被乏味的汽车/交通理论闷死。

    Bullet像Ogre一样,提供了一个DemoApplication类,方便我们学习,我们先看看Bullet的DemoApplication是怎么样的。先看看Bullet自己提供的AppBasicDemo这个Demo。忽略那些作者用#if 0关闭的内容和hashmap的测试内容,看看DemoApplication的用法。

首先是BasicDemo类,从class BasicDemo : public PlatformDemoApplication可以看到,DemoApplication是给你继承使用的,这里的PlatformDemoApplication实际是GlutDemoApplication。(Win32那个作者好像只是预留的)

怎么去实现这个类先放一边,看看整个类的使用:

    GLDebugDrawer    gDebugDrawer;

    BasicDemo ccdDemo;

    ccdDemo.initPhysics();

    ccdDemo.getDynamicsWorld()->setDebugDrawer(&gDebugDrawer);

    glutmain(argc, argv,640,480,"Bullet Physics Demo. http://bulletphysics.com",&ccdDemo);

实际就这5句,很简单,构造debug,BasicDemo,调用initPhysics函数,设定debug,调用glutmain这个函数,参数也一目了然。这里就不看了。看实现一个有用的DemoApplication的过程。

    大概看看DemoApplication这个基类和GlutDemoApplication知道必须要实现的两个纯虚函数是

virtual    void initPhysics() = 0;

virtual void clientMoveAndDisplay() = 0;

看BasicDemo的实现后,知道还需要实现displayCallback这个现实回调,基本上就没有其他东西了,理解起来也还算容易。

initPhysics的部分,一看就知道,与HelloWorld中过程几乎一致,也就是实际构建物理世界的过程。只是多了   

setTexturing(true);

setShadows(true);

setCameraDistance(btScalar(SCALING*50.));

这三个与显示有关的东西(其实这些代码放到myinit中去也可以,毕竟与物理无关)

最后还多了个clientResetScene的调用,我们知道这个过程就好,具体函数的实现先不管。

clientMoveAndDisplay和displayCallback部分

其实非常简单,几乎可以直接放到glutExampleApplication中去。(事实上不从灵活性考虑,我觉得放到glutExampleApplication中更好)

原来的程序有些代码重复,其实只要下列代码就够了:(一般的程序也不需要修改)
void
 BasicDemo::clientMoveAndDisplay()

{

    //simple dynamics world doesn't handle fixed-time-stepping

    float
 ms = getDeltaTimeMicroseconds();

    

    ///step the simulation

    if
 (m_dynamicsWorld)

    {

        m_dynamicsWorld->stepSimulation(ms / 1000000.f
);

    }

        

    displayCallback();

}

void
 BasicDemo::displayCallback(void
) {

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    

    renderme();

    //optional but useful: debug drawing to detect problems

    if
 (m_dynamicsWorld)

        m_dynamicsWorld->debugDrawWorld();

    glFlush();

    swapBuffers();

}

运行该程序能够看到中间一个很多Box堆起来的大方块,点击鼠标右键还能发射一个方块出去。

了解这个Demo以后,我们就可以直接来用Bullet构建我们自己的物理世界了,暂时不用考虑图形的问题,甚至不用知道Bullet使用GLUT
作为debug图形的输出,GLUI
做界面,都不用知道,只需要知道上面demoApplication的使用和在initPhysics函数中完成构建物理世界的代码。另外,你愿意的话,也可以先看看exitPhysics的内容,用于分配资源的释放,作为C++程序,一开始就关注资源的释放问题是个好习惯。虽然对于我们这样简单的demo程序来说是无所谓的。

看过上面Demo后,也许你已经有些了解,也许你还是一头雾水,不管怎么样,Bullet的Demo毕竟还是别人的东西,现在,从零开始,构建一个HelloWorld程序描述的世界。先自己尝试一下!假如你成功了,那么直接跳过一下的内容,失败了,再回头了看看,提醒你步骤:

1.继承DemoApplication,拷贝上面clientMoveAndDisplay和displayCallback部分的代码,实现这两个函数。

2.在initPhysics函数中完成构建物理世界的代码。(构建过程参考HelloWorld)

3.Main中的使用代码:

    GLDebugDrawer    gDebugDrawer;

    BasicDemo ccdDemo;

    ccdDemo.initPhysics();

    ccdDemo.getDynamicsWorld()->setDebugDrawer(&gDebugDrawer);

    glutmain(argc, argv,640,480,"Bullet Physics Demo. http://bulletphysics.com",&ccdDemo);

4.注意工程需要多包含$(BULLET_HOME)/Demos/OpenGL的头文件目录

和库:

$(BULLET_HOME)/Glut/glut32.lib

opengl32.lib

glu32.lib

麻烦点的是glut是个动态库,你需要将dll文件拷贝到你工程的运行目录。

现在应该成功了吧?

我实现的工程见https://bullet-sample.jtianling.googlecode.com/hg/中的Bullet-WithGL。

于是乎,现在你已经可以看到Hello World中那个不曾显示的电脑凭空现象的球了。大概是下面这个样子滴:(因为现在Google Docs写完文章后很久就不能够直接post到CSDN的博客中去了,所以每次写完文章后都得到新建文章中去复制粘贴,图片还需要重新上传然后插入,非常麻烦,所以最近的文章都尽量的减少了图片的使用,见谅。其实说来,只有看热闹的人才需要截图,真的看教程的人估计自己的程序都已经运行起来了,也没有必要看我的截图了)


到目前为止,你已经有了可以自己happy一下的程序了,你可以立即深入的学习,去研究ExampleApplication的源代码,去了解Bullet是怎么与图形交互的,但是在这个之前,特别是对于以前没有使用过其他物理引擎的人,先多在这个图形版的HelloWorld的程序的基础上玩玩,比如现在球很明显没有弹性,调整一下反弹的系数看看,比如多生成几个球,比如加上速度,演示两个球的碰撞,比如因为现在没有设置debugmode,所以实际没有debug信息输出,尝试输出aabb等debug信息,有助于进一步学习。有了图形,就有了丰富的世界,先对Bullet的各个概念先熟悉一下(特别是btRigidBodyConstructionInfo中的各个变量,还有各个shape)然后再前进吧。事实上,得益于ExampleApplication,现在甚至可以用鼠标左键拖拽物理,右键发射箱子的功能也还在,还能按左右键调整camera。(其实还有一堆功能,自己尝试一下吧)因为比较简单,在本教程中不会再有关于这些基础信息的内容,只能自己去找资料或者尝试了。其实就我使用物理引擎的经验,学习并使用一款物理引擎并不会太难,最麻烦的地方在于后期游戏中各个参数的调整,尽量使游戏的效果变得真实,自然,或者达到你想要的效果,这种调整很多时候需要靠你自己的感觉,而这个感觉的建立,那就是多多尝试一个物理引擎中的在各个参数下呈现的不同效果了,这种感觉的建立,经验的获得,不是任何教程,文档或者演示程序能够给你的。

比如,下面是Bullet支持的5种基本物体形状:


其实上面的内容是最关键的,此时学开车的你已经在司机的位置了,学游泳的你已经在水里了,剩下的Bullet相关的内容虽然还有很多,但是其实已经完全可以自己独立折腾了,因为每个折腾的成果你都已经能够实时的看到,能够很哈皮的去折腾了。

与显示的整合,MotionState

    一个只有数据运算的物理引擎,一般而言只能为显示引擎提供数据,这就牵涉到与图形引擎整合的问题,像Box2D这样的物理引擎就是直接需要直接向各个物理实体去查询位置,然后更新显示,这种方式虽然简单,但是我感觉非常不好,因为难免在update中去更新这种东西,导致游戏逻辑部分需要处理物理引擎+图形引擎两部分的内容。(可以参考Box2D与Cocos2D for iPhone的整合)而且,对于完全没有移动的物体也会进行一次查询和移动操作。(即使优化,对不移动物体也是进行了两次查询)

    Bullet为了解决此问题,提供了新的解决方案,MotionState。其实就是当活动物体状态改变时提供一种回调,而且就Bullet的文档中说明,此种回调还带有适当的插值以优化显示。通过这种方法,在MotionState部分就已经可以完成显示的更新,不用再需要在update中添加这种更新的代码。而且,注意,仅仅对活动物体状态改变时才会进行回调,这样完全避免了不活动物体的性能损失。

    首先看看ExampleApplication中是怎么利用default的MotionState来显示上面的图形的,然后再看看复杂点的例子,与Ogre的整合。

先看看回调接口:

///The btMotionState interface class allows the dynamics world to synchronize and interpolate the updated world transforms with graphics

///For optimizations, potentially only moving objects get synchronized (using setWorldPosition/setWorldOrientation)

class
   btMotionState

{

    public
:

        

        virtual
 ~btMotionState()

        {

            

        }

        

        virtual
 void
  getWorldTransform(btTransform& worldTrans ) const
 =0
;

        //Bullet only calls the update of worldtransform for active objects

        virtual
 void
  setWorldTransform(const
 btTransform& worldTrans)=0
;

        

    

};

很简单,一个get接口,用于bullet获取物体的初始状态,一个set接口,用于活动物体位置改变时调用以设置新的状态。

下面看看btDefaultMotionState这个bullet中带的默认的MotionState类。

///The btDefaultMotionState provides a common implementation to synchronize world transforms with offsets.

struct
  btDefaultMotionState : public
 btMotionState

{

    btTransform m_graphicsWorldTrans;

    btTransform m_centerOfMassOffset;

    btTransform m_startWorldTrans;

    void
*      m_userPointer;

    btDefaultMotionState(const
 btTransform& startTrans = btTransform::getIdentity(),const
 btTransform& centerOfMassOffset = btTransform::getIdentity())

        : m_graphicsWorldTrans(startTrans),

        m_centerOfMassOffset(centerOfMassOffset),

        m_startWorldTrans(startTrans),

        m_userPointer(0
)

    {

    }

    ///synchronizes world transform from user to physics

    virtual
 void
   getWorldTransform(btTransform& centerOfMassWorldTrans ) const
 

    {

            centerOfMassWorldTrans =    m_centerOfMassOffset.inverse() * m_graphicsWorldTrans ;

    }

    ///synchronizes world transform from physics to user

    ///Bullet only calls the update of worldtransform for active objects

    virtual
 void
   setWorldTransform(const
 btTransform& centerOfMassWorldTrans)

    {

            m_graphicsWorldTrans = centerOfMassWorldTrans * m_centerOfMassOffset ;

    }

};

这个默认的MotionState实现了这两个接口,但是还引入了质心
(center Of Mass应该是指质心
吧)的概念,与外部交互时,以质心位置表示实际物体所在位置。

在一般rigitBody的构造函数中可以看到下列代码:

    if
 (m_optionalMotionState)

    {

        m_optionalMotionState->getWorldTransform(m_worldTransform);

    } else

    {

        m_worldTransform = constructionInfo.m_startWorldTransform;

    }

这就是get函数的使用,也就是决定物体初始坐标的函数回调。

set函数的回调如下:

void
    btDiscreteDynamicsWorld::synchronizeSingleMotionState(btRigidBody* body)

{

    btAssert(body);

    if
 (body->getMotionState() && !body->isStaticOrKinematicObject())

    {

        //we need to call the update at least once, even for sleeping objects

        //otherwise the 'graphics' transform never updates properly

        ///@todo: add 'dirty' flag

        //if (body->getActivationState() != ISLAND_SLEEPING)

        {

            btTransform interpolatedTransform;

            btTransformUtil::integrateTransform(body->getInterpolationWorldTransform(),

                body->getInterpolationLinearVelocity(),body->getInterpolationAngularVelocity(),m_localTime*body->getHitFraction(),interpolatedTransform);

            body->getMotionState()->setWorldTransform(interpolatedTransform);

        }

    }

}

也就是同步状态的时候调用。此过程发生在调用bullet的btDynamicsWorld::stepSimulation函数调用时。

然后可以参考DemoApplication的DemoApplication::renderscene(int pass)函数:

    btScalar    m[16
];

    btMatrix3x3 rot;rot.setIdentity();

    const
 int
  numObjects=m_dynamicsWorld->getNumCollisionObjects();

    btVector3 wireColor(1
,0
,0
);

    for
(int
 i=0
;i<numObjects;i++)

    {

        btCollisionObject*  colObj=m_dynamicsWorld->getCollisionObjectArray()[i];

        btRigidBody*        body=btRigidBody::upcast(colObj);

        if
(body&&body->getMotionState())

        {

            btDefaultMotionState* myMotionState = (btDefaultMotionState*)body->getMotionState();

            myMotionState->m_graphicsWorldTrans.getOpenGLMatrix(m);

            rot=myMotionState->m_graphicsWorldTrans.getBasis();

        }

    }

}

实际也就是再通过获取motionState然后获取到图形的位置了,这种defaultMotion的使用就类似Box2D中的使用了。

既然是回调,那么就可以让函数不仅仅做赋值那么简单的事情,回头来再做一次轮询全部物体的查询,官网的WIKI中为Ogre编写的MotionState就比较合乎推荐的MotionState用法,代码如下:

lass MyMotionState : public
 btMotionState {
public
:

    MyMotionState(const
 btTransform &initialpos, Ogre::SceneNode *node) {

        mVisibleobj = node;

        mPos1 = initialpos;

    }

    virtual
 ~MyMotionState() {

    }

    void
 setNode(Ogre::SceneNode *node) {

        mVisibleobj = node;

    }

    virtual
 void
 getWorldTransform(btTransform &worldTrans) const
 {

        worldTrans = mPos1;

    }

    virtual
 void
 setWorldTransform(const
 btTransform &worldTrans) {

        if
(NULL
 == mVisibleobj) return
; // silently return before we set a node

        btQuaternion rot = worldTrans.getRotation();

        mVisibleobj->setOrientation(rot.w(), rot.x(), rot.y(), rot.z());

        btVector3 pos = worldTrans.getOrigin();

        mVisibleobj->setPosition(pos.x(), pos.y(), pos.z());

    }

protected
:

    Ogre::SceneNode *mVisibleobj;

    btTransform mPos1;

};

注意,这里的使用直接在set回调中直接设置了物体的位置。如此使用MotionState后,update只需要关心逻辑即可,不用再去手动查询物体的位置,然后更新物体的位置并刷新显示。

碰撞检测

    物理引擎不仅仅包括模拟真实物理实现的一些运动,碰撞,应该还提供方式供检测碰撞情况,bullet也不例外。

    AppCollisionInterfaceDemo展示了怎么直接通过btCollisionWorld来检测碰撞而不模拟物理。

而官方的WIKI对于碰撞检测的描述也过于简单,只给下列的示例代码,但是却没有详细的解释。

    //Assume world->stepSimulation or world->performDiscreteCollisionDetection has been called

    int
 numManifolds = world->getDispatcher()->getNumManifolds();

    for
 (int
 i=0
;i<numManifolds;i++)

    {

        btPersistentManifold* contactManifold =  world->getDispatcher()->getManifoldByIndexInternal(i);

        btCollisionObject* obA = static_cast
<btCollisionObject*>(contactManifold->getBody0());

        btCollisionObject* obB = static_cast
<btCollisionObject*>(contactManifold->getBody1());

    

        int
 numContacts = contactManifold->getNumContacts();

        for
 (int
 j=0
;j<numContacts;j++)

        {

            btManifoldPoint& pt = contactManifold->getContactPoint(j);

            if
 (pt.getDistance()<0.f
)

            {

                const
 btVector3& ptA = pt.getPositionWorldOnA();

                const
 btVector3& ptB = pt.getPositionWorldOnB();

                const
 btVector3& normalOnB = pt.m_normalWorldOnB;

            }

        }

    }

以上代码的主要内容就是
int
 numManifolds = world->getDispatcher()->getNumManifolds();

btPersistentManifold* contactManifold =  world->getDispatcher()->getManifoldByIndexInternal(i);

两句。

    而btPersistentManifold类表示一个Manifold,其中包含了body0,body1表示Manifold的两个物体。

这里特别提及的是,Manifold并不直接表示碰撞,其真实的含义大概是重叠,在不同的情况下可能表示不同的含义,比如在Box2D中,手册的描述大概是(凭记忆)为了快速的检测碰撞,在2D中一般先经过AABB盒的检测过滤,而只有AABB盒重叠的才有可能碰撞,而Manifold在Box2D中就表示AABB盒重叠的两个物体,而我看Bullet有不同的Broadphase,在实际中,也重叠也应该会有不同的情况,因为我没有看源码,所以不能确定,但是,总而言之,可以理解Manifold为接近碰撞的情况。

所以无论在Box2D还是Bullet中,都有额外的表示碰撞的概念,那就是contact(接触)。上述示例代码:
int
 numContacts = contactManifold->getNumContacts();

就表示查看接触点的数量,假如接触点为0,那么自然表示两个物体接近于碰撞,而实际没有碰撞。而上述代码中的Distance的判断应该是防止误差,因为我输出了一个盒子和地面发生碰撞的全部过程的distance,发现绝大部分情况,只要有contact,那么距离就小于0,可是在一次盒子离开地面的过程中,distance还真有过一次0.00x的正值。。。。。。。

当你开始放心大胆的使用上述代码后,也许你总是用来模拟物体的其他效果,也许都不会有问题,直到某一天你希望在碰撞检测后删除掉发生碰撞的问题,你的程序crash了。。。。你却不知道为什么。用前面的demo来展示碰撞检测的方法,并且删除掉发生碰撞的物体。一般先写出的代码都会类似下面这样:

    int
 numManifolds = m_dynamicsWorld->getDispatcher()->getNumManifolds();

    for
 (int
 i=0
;i<numManifolds;i++)

    {

        btPersistentManifold* contactManifold =  m_dynamicsWorld->getDispatcher()->getManifoldByIndexInternal(i);

        btCollisionObject* obA = static_cast
<btCollisionObject*>(contactManifold->getBody0());

        btCollisionObject* obB = static_cast
<btCollisionObject*>(contactManifold->getBody1());

        int
 numContacts = contactManifold->getNumContacts();

        for
 (int
 j=0
;j<numContacts;j++)

        {

            btManifoldPoint& pt = contactManifold->getContactPoint(j);

            if
 (pt.getDistance()<0.f
)

            {

                RemoveObject(obA);

                RemoveObject(obB);

            }

        }

    }

但是上面这样的代码是有问题的,这在Box2D的文档中有详细描述,Bullet文档中没有描述,那就是obA和obB可能重复删除的问题(也就相当于删除同一个对象多次,自然crash)在本例中有两个问题会导致重复,很明显的一个,当两个物体多余一个Contact点的时候,在遍历Contacts点时会导致obA,obB重复删除。另外,稍微隐晦点的情况是,当一个物体与两个物体发生碰撞时,同一个物体也可能在不同的manifold中,所以,真正没有问题的代码是先记录所有的碰撞,然后消除重复,再然后删除
。这是Bullet文档中没有提到,WIKI中也没有说明的,初学者需要特别注意。。。。。。下面才是安全的代码:

    int
 numManifolds = m_dynamicsWorld->getDispatcher()->getNumManifolds();

    for
 (int
 i=0
;i<numManifolds;i++)

    {

        btPersistentManifold* contactManifold =  m_dynamicsWorld->getDispatcher()->getManifoldByIndexInternal(i);

        btCollisionObject* obA = static_cast
<btCollisionObject*>(contactManifold->getBody0());

        btCollisionObject* obB = static_cast
<btCollisionObject*>(contactManifold->getBody1());

        int
 numContacts = contactManifold->getNumContacts();

        for
 (int
 j=0
;j<numContacts;j++)

        {

            btManifoldPoint& pt = contactManifold->getContactPoint(j);

            if
 (pt.getDistance()<0.f
)

            {

                m_collisionObjects.push_back(obA);

                m_collisionObjects.push_back(obB);

            }

        }

    }

    m_collisionObjects.sort();

    m_collisionObjects.unique();

    for
 (CollisionObjects_t::iterator itr = m_collisionObjects.begin();

        itr != m_collisionObjects.end();

        ++itr) {

        RemoveObject(*itr);

    }

    m_collisionObjects.clear();

上述m_collisionObjects是std::list类型的成员变量。

碰撞过滤

    Bullet的wiki
提到了3个方法,这里只讲述最简单的mask(掩码)过滤方法。

    mask的使用相信大家基本都接触过,无非就是通过一个整数各个2进制位来表示一些bool值。比如Unix/Linux中文件权限的掩码。在bullet中的碰撞mask的使用非常简单,主要在addRigidBody时候指定。(需要注意的是,只有btDiscreteDynamicsWorld类才有这个函数,btDynamicsWorld并没有,所以demoApplication中的成员变量dynamicWorld不能直接使用。)

WIKI中的代码已经很能说明问题了:

#define BIT(x) (
1
<<(x))

enum
 collisiontypes {

    COL_NOTHING = 0
, //<Collide with nothing

    COL_SHIP = BIT(1
), //<Collide with ships

    COL_WALL = BIT(2
), //<Collide with walls

    COL_POWERUP = BIT(3
) //<Collide with powerups

}

int
 shipCollidesWith = COL_WALL;
int
 wallCollidesWith = COL_NOTHING;
int
 powerupCollidesWith = COL_SHIP | COL_WALL;

btRigidBody ship; // Set up the other ship stuff

btRigidBody wall; // Set up the other wall stuff

btRigidBody powerup; // Set up the other powerup stuff

mWorld->addRigidBody(ship, COL_SHIP, shipCollidesWith);

mWorld->addRigidBody(wall, COL_WALL, wallCollidesWith);

mWorld->addRigidBody(powerup, COL_POWERUP, powerupCollidesWith);

特别是那个#define BIT(x) (
1
<<(x))
宏用的很有意思。

不要特别注意的是,两个物体要发生碰撞,那么,两个物体的collidesWith参数必须要互相指定对方,假如A指定碰撞B,但是B没有指定碰撞A,那么还是没有碰撞。就上面的例子而言,虽然ship和powerup想要撞墙,但是墙不想撞它们,那么事实上,上面的例子就相当于过滤了所有墙的碰撞,其实仅仅只有ship和power的碰撞,这真所谓强扭的瓜不甜啊,等双方都情愿。

仿照上面的例子,假如你希望在碰撞检测的时候过滤掉地板,只让物体间发生碰撞然后删除物体,为demo添加下列代码:

#define BIT(x) (
1
<<(x))

    enum
 collisiontypes {

        COL_NOTHING = 0
, //<Collide with nothing

        COL_GROUND = BIT(1
), //<Collide with ships

        COL_OBJECTS = BIT(2
), //<Collide with walls

    };

    short
 GroundCollidesWith = COL_OBJECTS;

    short
 ObjectsCollidesWith = COL_GROUND;

但是当你将上述方法应用到demo中,想要过滤掉你想要的碰撞,你会发现碰撞检测的确是过滤掉了,同时过滤掉的还有碰撞,球直接传地板而过,掉进了无底的深渊。注意,这里的过滤是指碰撞过滤,而不是碰撞检测的过滤,假如希望实现碰撞检测的过滤,你可以在碰撞检测中直接进行。比如前面地板的例子,因为地板是静态物体,你可以通过调用rigidBody的isStaticObject来判断是否是地板,然后进行删除,就如下面的代码这样:

            if
 (pt.getDistance()<0.f
) {

                if
 (!obA->isStaticObject()) {

                    m_collisionObjects.push_back(obA);

                }

                if
 (!obB->isStaticObject()) {

                    m_collisionObjects.push_back(obB);

                }

            }

假如希望与地面碰撞并不删除物体,只有物体与物体的碰撞才删除物体,这也简单:

                if
 (!obA->isStaticObject() && !obB->isStaticObject()) {

                    m_collisionObjects.push_back(obA);

                    m_collisionObjects.push_back(obB);

至于更加复杂的情况,还可以借助于rigidBody的UserPointer,这在WIKI中没有提及,

    ///users can point to their objects, userPointer is not used by Bullet

    void
*  getUserPointer() const

    {

        return
 m_userObjectPointer;

    }

    

    ///users can point to their objects, userPointer is not used by Bullet

    void
   setUserPointer(void
* userPointer)

    {

        m_userObjectPointer = userPointer;

    }

但是就我的经验,这两个函数的作用是巨大的,你可以将你需要的一切都设置进去。。。。。。。。然后取出来,就上面的碰撞检测过滤而言,你完全可以实现自己的一套碰撞检测mask,只要你想,一切皆有可能。这些例子的完整源代码见https://bullet-sample.jtianling.googlecode.com/hg/中的Bullet-CollideDetection工程。

约束(Constraints)和连接(Joints)

    一个一个单独的物理实体已经可以构建一个有意思的物理世界了,但是显示世界有很多东西(最典型的就是绳子连接的物体)不是单独的物理实体可以模拟的,物理引擎中使用约束来模拟类似的东西/现象。

    (待补充)

软体

   因为我的使用暂时不需要用到软体,暂时未学习此部分内容,欢迎大家补充。

有用的工具

1. MAYA,3D Max的插件

2. Blender
,开源的3D建模工具,内建的Game Engine有直接的Bullet支持,还有Erwin提供的改版
可以直接导出.bullet文件。

使用了Bullet的其他有用工程

1. GameKit
,Erwin Coumans自己发起的整合Ogre/Irrlicht和Bullet的游戏引擎,与Blender结合的也很好。

2. oolongengine
,乌龙引擎,wolfgang.engel
他的博客
)这个大牛(到底有多牛可以参考这里
)发起的iPhone平台引擎项目,使用了Bullet,也为Bullet能够流畅的运行于iPhone平台做出了很大贡献。(优化了浮点运算)为什么写乌龙引擎?wolfgang自己有个解释,见这里

3. Dynamica
, Erwin建立的工程,开发bullet的Maya,3D Max插件。

4. bullet-physics-editor
, Erwin自己发起的一个bullet编辑器工程,目前还处于前期开发阶段。但是项目中同时包含一些能够到处.Bullet文件的Blender改版

Bullet的实现原理

1.Physics Pipeline Implementation
,应该是一个在爱尔兰的中国人写的,并发布在WIKI上,这里是他的博客

2.SIGGRAPH 2010 course slides, high-level overview on Bullet collision detection

3.GDC 2010 presentation about contact generation

4.最大的资料自然就是Bullet的源代码
啦。。。。。。慢慢研究吧。

参考资料

1.Bullet 2.76 Physics SDK Manual
,Bullet的项目发起人,目前的负责人Erwin Coumans所写,(就WIKI资料显示,这哥们现在在SONY)算是官方的Manual了,源码包中就有pdf。

2.Bullet WIKI Tutorial Articles
,算是第2个能够找到的稍微好点的关于Bullet的东西了,就是有点散乱。

3.Bullet Bullet Documentation
,Bullet的文档,自动生成的,也就只能在写代码的时候可能有些用,很难靠这个学习。

 

 

我写的关于Bullet的文章

1.Ogre与Bullet的整合(on iPhone)

2.Ogre的3D Max插件介绍

 

最后,因为本文较长,为方便查看,提供了pdf版
方便大家查看。

 

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

谢尔宾斯基三角(Sierpinski triangle)的显示 (with OpenGL)

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

    Sierpinski triangle
的图形我早就见过了,是很漂亮的分形图形,只是今天才知道它叫这么个复杂的名字,很显然是以某个人命名的,因为我不研究分形,所以也不管他了。
   
只是最近看《Interactive Computer Graphics》(5nd Edward
Angel著)一书的时候,作者在讲解OpenGL
API的时候就引入了这个有意思的图形,本书作为讲图形学的技术书籍(还是作为美国标准教材类型写作的),算是别具趣味性了。里面有几个绘制
Sierpinski triangle的例子(书中称此图形为Sierpinski
gasket,是一样的),算是符合我以前学习的精神,用学到的最少的几个知识点,鼓捣出最大的乐趣。^^原文中的例子都是仅带源代码文件,(不带工程)
源代码还是仅仅只能一个一个下载,而且显示的时候是一次性显示的,这个我感觉不太爽,不能显示出这种分形图形生成时那种渐变的感觉,特别是用点随机生成的
那种类似粒子系统的乱中有序的效果,于是乎,一方面,给我感兴趣的原始版本配上XCode工程,(需要VS工程的就只能自己鼓捣了)另外一方面,提供动态
生成图形的例子,也算是加深理解。。。。。。

阅读全文....

国外大牛对几大著名开源图形/游戏引擎的点评

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

    其实早就看到了(很老了,写于Sunday, December 9, 2007),最近写Bullet相关文章的时候又翻看到了,想到常常有人会问到底哪个开源引擎比较好,这里就顺便简单的翻译一下吧。虽然是一家之言,而且Wolfgang Engel这个大牛(不了解有多牛可以参考这里
)的话是针对移动平台的,但是还是有一些普遍的参考价值的。来源
。这也是Wolfgang写乌龙引擎(oolongengine
)的初衷。

 

译文:

(括号里面的话都是我加的,单纯的翻译好无聊啊。。。。。。。)

对几大开源引擎的点评:

Ogre:架构和设计不太关注效率。其C++的用法导致使用和重新设计都比较困难。一个例子:每个材质都有它自己的一个C++文件并且这里还有一个复杂的继承链.... (现在Ogre已经官方支持iPhone了,也不需要Wolfgang来移植了,但是效率的确是个问题)

Irrlicht:我尝试的Mac OS X版本看起来像Quake 3引擎。它看起来缺少很多现代3D引擎设计的元素,除了看起来非常适合移动设备。因此,你可能也就想使用原来的Quake3引擎..... (的确很适合移动设备)

Quake 3:这是个很显然有很多优秀工具,非常有效率的引擎,我以前开发荣誉勋章系列的时候就使用了这个引擎,但是我想要更加灵活一些,并且我想以更加高级的硬件为目标。 (Quake 3实在也太老了点,现在也支持iPhone了)

Crystal Space:为什么每个东西都是一个插件?想不通。

C4:这是我最习惯的引擎之一,但是它是闭源的:-(  (对开源引擎的评价怎么会加上这个闭源引擎?-_-!)

所以,我现在想要写我自己的,基于底层框架的引擎。(也就是乌龙引擎)

 

原文:

Evaluated several open source engines:

  • Ogre:
    the architecture and design is not very performance friendly. The usage
    of C++ makes the usage and re-design here quite difficult. An example:
    each material has its own C++ file and there is an inheritance chain
    from a base class ...
  • Irrlicht: the Mac OS X version I tried
    looks like a Quake 3 engine. It also seems to lack lots of design
    elements of a modern 3D engine. Other than this it looks quite good for a
    portable device. You might also use the original Quake 3 engine then
    ...
  • Quake 3: this is obviously a very efficient game engine with
    rock-solid tools, I worked with this engine in the Medal of Honor
    series before, but I wanted a bit more flexibility and I wanted to
    target more advanced hardware.
  • Crystal Space: why is everything a plug-in? Can't get my head around this.
  • C4: this is one of my favourite engines, but it is closed source :-(

So I want to write my own based on the low-level framework I have in place now.

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

整合Bullet物理引擎到Ogre on iPhone

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

为什么选择Bullet

    Bullet
算是一个比较流行的3D物理引擎了,大概的看了几眼以后,了解了一些基本用法,发现很多3D物理中的概念与2D(比如Box2d)中的概念是相同的,甚至,Bullet的一些用法都与Box2D类似。基本了解以后,对于我来说,那就是iPhone平台的问题了。
    我选择Bullet而不是其它一大堆同样著名的物理引擎,主要源自乌龙(oolongengine
)
引擎,该引擎由wolfgang.engel最先创建,并且内嵌了bullet的支持,竟然如此大牛都青睐的引擎,我怎么能无视呢?何
况,oolongengine的使用,也说明Bullet在iPhone中使用完全没有问题。另外,还有Blender这个非常著名的3D建模工具也是对
Bullet有直接支持,可见Bullet的流行程度。事实上,还有些故事,比如oolongengine的项目负责人之一erwin.coumans,
同时就也是Bullet的项目创建者兼现在的负责人。(到Google
Code上去看看就知道了)并且,erwin提到,Bullet能够流畅的在iPhone上运行,wolfgang提供了很大的帮助,对浮点运算进行了特
别优化。最有意思的是,迪斯尼公司,自己的一些项目用到了Bullet,(看主页上的介绍,起码玩具总动员3这个游戏用到了Bullet)所以开发了自己
的MAYA
Bullet插件,为了回报开源社区,已经将此插件开源了。。。。。感谢Bullet,也感谢迪斯尼,同时感叹国外这种开源社区之间的交互。。。。。呵
呵,这才叫欣欣向荣的良性发展,你帮助我,我帮助你,公司受到帮助,也对社区进行回报。知道了这么多故事以后,更加是对Bullet多了很多好感。最值得
一提的是,erwin
简直就是个开源狂人。。。。。他还发起过一个叫做gamekit
的开源游戏引擎项目,希望整合Ogre/Irrlicht和Bullet,因为是erwin创建的项目,也非常值得期待。。。。。。。
    闲话多说一向是我的毛病。。。。也就说到这里了,用以前的OGRE on iPhone
工程直接开工了。

在XCode中编译Bullet和OgreBullet

   
初下载Bullet后,用CMake做工程,只能做Mac OS
X的工程,没有iPhone的选项,于是参考一下乌龙引擎的做法,就是将整个Src目录都拷贝进自己的工程,好像是从iPhone开始,流行这种"暴力"
使用源代码的方式了。。。。。只能说Apple的XCode开发的还不足够人性化,所以建库的工程没有VS那么方便,再加上iPhone天生的不支持动态
库,更加助长了这种“暴力”使用源代码的方式,其实每次修改工程文件编译都会慢很多,无奈啊。。。。。在Bullet的论坛中,搜索到erwin的准官方解决方案
就是拷贝全部目录。。。。汗一个-_-!
    既然如此,一切倒是简单了。。。。。下载Bullet的源码
,目前最新的是2.77,拷贝Src目录,删掉无用文件,比如CMake的一些文件。(或者直接从oolong引擎中将整个Bullet目录拷贝过来最简单,只不过版本目前是2.73)然后配置Bullet的Include目录,编译,一切OK。
   

现在开始尝试嵌入OgreBullet,方法还是直接包含源代码。比较特殊点的是OgreBullet需要用到Bullet的
ConvexDecomposition,这个库在Bullet的Extra中,也将源代码都拖过来,然后弄好include目录,就没有问题了。

测试

    现在进入测试阶段,就用OgreBullet的Tutorial

的例子。源代码全部拷贝过来,唯一的问题是ExampleApplication在iPhone中有些小问题,修改一下函数,namespace后问题解
决。运行时崩溃,查看问题,还是ExampleApplication这个类的问题,难怪在Ogre的iPhone
template不用这个类,崩溃的地方很有意思,OIS获取键盘的时候:
mKeyboard = static_cast<OIS::Keyboard*>(mInputManager->createInputObject( OIS::OISKeyboard, bufferedKeys ));
注释更加有意思:
//Create all devices (We only catch joystick exceptions here, as, most people have Key/Mouse)
典型反映了代码永远赶不上时代变化,既然这个类已经不被人使用了,我也就不费劲去用了,将原来的例子代码全部嵌入到OgreFramework类中。
运行,崩溃,发现忘了添加新增的资源,将BumpyMetal.jpg材质和cube.mesh模型添加进工程。再次运行,一些正常,有图有真相:

当一切都OK以后,我发现我的目标竟然与GameKit是一样的。。。。。不就是Ogre+Bullet吗?erwin
估计以前就已经想过我所想了。。。。。也许,尝试下GameKit也不错。

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

Bullet的3D Max插件

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

准备

    早就知道Bullet有个比较好的MAYA插件,那是迪斯尼自己的东西,然后开源的,在bullet的网站就有下载
。但是一直一来都是学习3D Max,接触了一下MAYA,感觉不是太习惯,有人说MAYA的操作方式接近设计师的思维。。。。我果然不是个设计师啊。。。。。我甚至都准备转投Blender了,好歹那也是开源免费的东西,而且与Bullet结合的还不错,还有GameKit这个好玩的东西在。我初步的看了一下,感觉GameKit有点类似LOVE这个Lua的2D游戏引擎,以一个数据包作为输入,然后是一个独立的可执行程序,只不过LOVE中的数据包是zip包,集合了一堆的Lua脚本和资源,而GameKit的数据包是由Blender生成的,也可以包含Lua脚本,正在想呢,这个东西是不是太前卫啊?在Blender中通过一堆的UI去弄逻辑?作为程序员,好像还是比较习惯编写代码。。。。。呵呵,为了Bullet,前段时间刚刚了解了一下Blender的2.4x版本,结果发现GameKit支持的blender的新版(有点想3D Max了)界面差异好大啊。。。也是个打击。。。。。就在此时,无意识的(我也不知道为什么)到Bullet的插件独立网站dynamica

中去逛了一下,才发现,原来Bullet已经有3D Max的插件了,改装自Nvidia开源的PhyX的3D Max插件,(感谢伟大的NVidia公司,虽然它为了自家的Cg对OpenGL的GLSL有些冷遇,导致我需要用AMD的显卡来运行RenderMonkey才方便学习,不过因此我都原谅他了-_-!)甚至还有最新的2011版本的。。。。。天哪,我都在费什么劲啊。。。。晃了那么大一圈,果然,人生在于折腾。。。。。暂时告别下MAYA和Blender了,继续弄弄3D Max先。

    安装完插件后,发现Bullet的3D Max插件文档也比MAYA的要丰富啊(MAYA的主要文档就是指向几个教学视频)。。。。。太感谢开源社区了。。。。。。安装后的3D Max效果如图:

实验

有些奇怪的是,安装完后虽然有Example目录,文档中也提到过Example,但是实际没有任何Example,文档这么详细,使用这么简单,随便学学就能懂了,自己弄个Example^^

先用2D Array复制出一些堆起来的盒子,然后弄个球撞之。。。。。。。。。

呵呵,和文档中的例子截图有些像了^^同时发现复制时是可以带物理属性复制的,但是物理的模拟与3D Max的关键帧动画结合的并不好,不能回滚,导致我想回来截个好图都没有办法-_-!(只能重新再从头模拟一次)

经过试用,发现使用上还是有些不方便,比如每次修改物理属性后都需要add Selected或者add All Phys,这个不太合理,然后就是,目前完全不支持Joint或者Contrainter,也就是说,几乎也就是只有刚体和软体的支持。还有,初始速度和旋转不知道为啥,在我这里也无效,文档中没有提及。

说到这里,无法设定初始速度,那上面的例子是怎么做出来的呢?^^现在揭秘答案:

哈哈...............................

小结

    总的来说,bullet的3D Max插件已经比较不错了^^别说别的,就算当一个简单,方便的Bullet文件编辑器用都好啊,比起每个物体通过代码去设置属性,Bullet文件要方便太多了。最重要的是,3D Max对我来说,还是最亲切的。。。。。。暂时聊胜于无了,不够用的时候,再去求助于MAYA插件或者Blender吧。

 

注意:事实证明,目前的此插件到处的bullet文件无法正常加载,再未更新之前,请直接忽略之。

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

论XMarks的死掉及利益是第一生产力。。。。。。。。

    今天看到电脑玩物

的一则消息:根据XMarks的博客

发布的消息,由于找不到合适的盈利途径,XMarks将于2011年1月10日

停止服务。。。。。看看到这则消息我非常感叹,因为XMarks本身实在是一个非常好用的产品,也算是展示中国互联网环境到底多么开放的一个镜子,(Dropbox也是)一直以来,也根本没有替代品,(Firefox sync可以部分替代,但是XMarks是跨浏览器的)对于一个这么好的产品的死掉,总是引人无限的感叹,不禁引人深思,为什么一个好产品却不能生存下去?

    那就是一个产品的关键了 ----盈利,很明显,一个产品只有盈利才能长久的走下去,不能盈利的产品,无论多么优秀,无论用户的评价多么高,无论多么实用,无论满足了多少用户的需求(就像XMarks),最终还是会死掉。这是很悲哀,也很无奈的现实。现代社会发展的最大动力那就是利益,这是资本主义能成为地球上最成功的经济模式的基础,也是人类本性决定的。

    我也许会对迅雷,qq,暴风影音无时不刻的广告,突然弹出来影响你工作的广告感到非常的愤怒,也许会受不了搜狗的输入法在升级的时候还隐含带着浏览器的安装的流氓行为,然后连着输入法一起卸载了,并且在卸载原因里面告诉搜狗的人,卸载原因是因为你们做的就是流氓软件,也许会看着flashgot频繁的一次又一次仅仅升级版本号的更新,就是为了让你进去他的主页看看广告而多次想删掉它,但是在XMarks死掉后,也许我能原谅他们了。。。。。比起不想看到广告,我更不想看到它们死掉。。。。。

    以前有领导人提出,科学技术是第一生产力,事实上,我想其实他也知道,利益才是第一生产力,所以,他也是这样改革中国经济的。有利益的地方才有生产力。由此能想到app store的繁荣,而android的设备数量(指用户的持有量)虽然已经超过了iOS数量,但是android的程序却远远没有iOS上那么优秀,市场也远远没有那么繁荣,除了android的开发难度会更大(虽然JAVA容易,但是设备太杂),最大的原因其实大家都知道,因为apple培养了很多年的用户付费习惯,这是虽然有不亚于apple fans数量的,但是长期提供免费服务的google所最欠缺的,而,很现实的,能让开发者赚钱的地方,才有最多,最好的开发者。

    在中国,有的看起来还算懂经济的人甚至认为版权的保护是不重要的,认为应该自由,我看来非常的悲哀。。。。。无论是软件,电影,音乐,厂商没有利益,你甚至根本享受不到,而版权,咋看起来,那是保护厂商利益与用户作对的,但是事实上,也保护了用户的利益,因为只有他们存在盈利的可能,你才能有更好的软件可用。不要拿几个小小的特例来说事,(比如Unix的发展因为版权问题受到一些阻碍啥的),你能不能够想象一个没有版权保护的世界?

    还有观点认为版权啥的那是支撑着厂商暴利的东西,需要强烈反对,其实,这个世界暴利的东西只有权利,市场上实在太少,能够存在的时间也太短了,因为一旦暴利,其他人早就一拥而上了。而IT产品算是个比较特别的例子,我常常看到别人分析apple产品(比如iPhone)的成本,那就是将所有零部件的价格加起来,然后说apple实在暴利,其实我倒是不反对apple的东西比别的厂商利润高,但是这样计算的逻辑也实在太搞笑了,
那么软件行业就更特别了,
难怪有人同理提出windows的生产成本就3块钱,一个3块钱的东西,要中国人花几十块几百块去买,那就是天方夜谭了。其实这里有个小故事,我在gameloft工作时参与制作的spiderman,售价6.99美元,在app store 0.99就能买到很优秀的游戏时,这个售价还是比较高的了,所以我虽然的确第一时间购买,(仅仅因为自己开发过)但是还是和以前同事抱怨了一下,卖的好贵啊。。。。。同事的回答非常直接,看部电影还不几十块,这是消费观念的问题。的确,消费观念哪,在中国享受习惯了免费的软件,潜意识认为软件都可以免费用了,就算卖0.99美元(7块钱在中国其实还可以买什么?)都会觉得贵的。

    同理,为啥中国有着还算发达的软件业但是却几乎没有自己真正的好的软件产品/品牌,为啥今天中国的单机游戏,除了回合制RPG还是回合制RPG?(即使是回合制RPG都少了)很多人曾经说,中国的PC能够这么快的普及,盗版是个很重要的因素,因为降低了中国人使用PC的成本,所以上面其实是故意纵容的,但是其实从长久来看,损害的是整个中国的软件产业。从更大范围来看,也降低了很多国外厂商开发中文软件的兴趣,为啥那么多的软件,游戏有7,8国语言,但是却没有中文?因为这个原因,倒是助长了民间汉化的热情。其实总的来说,最后还是损害了用户自身的利益。

    有人反对仅仅谈及利益,所以有开源软件,但是同时,有人认为开源软件损害了真正软件开发者的利益,那是洪水猛兽。其实,当经济发展到一定地步,人类不愁吃,不愁穿了,才会有除了生产活动外的其它活动,比如娱乐,比如开源项目。大部分的开源项目,都还是以兴趣为基础的,而兴趣,不是连饭的吃不饱的人能够多想的,这也是中国很难有优秀的开源项目的原因。这也是为啥中国有的开发者(还在吃饭阶段)认为开源软件(发达国家已经脱离吃饭阶段了)损害了自己的利益。无论多少成功的开源项目最后创造了很大的经济效益(比如MySQL),那都仅仅是开源项目的特例而已。

    谈到开源,也许xmaks可以效仿blender,商业上失败,走开源的道路,起码最后还能活下来。。。。。

    其实写到这里,自己都不知道自己在写些什么了。。。。。仅仅作为看到XMarks死掉后的一次的感叹吧。


 

阅读全文....

OGRE On iPhone ----Ogre的iPhone基础模版框架源代码分析

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

    当想要在iPhone上使用某个3D引擎的时候,感觉我这水平,自己写好像还不现实,学到自己能写都不知道要到何年何月了,于是折腾过没有官方支持但是比较简单而且我比较熟悉的Irrlicht。虽然的确成功了(见我原来的文章

),
但是弄2D游戏的时候,(用其他引擎)经历过一些痛苦的事情后,我发现强大,成熟的引擎,以及官方的支持是多么重要。。。。。于是,虽然Ogre的效率在
iPhone上还有些难以承受,但是,我还是希望学习学习,并将Ogre作为优先使用的3D引擎。谁叫Ogre那么流行呢?将来要是弄老本行,去做网络游
戏,估计也不错^^

 

本文写作目的:

1.Ogre的基础模版程序

是Ogre在iPhone平台的模版程序的基础,但是此模版程序没有太多的说明和注释,作者仅仅是说程序具有自解释性。。。。。我这里代为简单解释一下。。。。

2.Ogre虽然是跨平台引擎,但是既然跨平台,在各平台就会有些差异存在,而从桌面程序到iPhone这样的移动平台,差异就更大了,我这里不从源码的角度,仅仅从示例程序对Ogre的使用角度指出这些差异。

而怎么在iPhone上编译运行Ogre,怎么样安装Ogre为iPhone做的项目模版,因为有官方支持嘛,下个SDK

,非常容易,不像Irrlicht那样需要调整很多东西,所以,这些都不是本文的重点,大家自己摸索一下吧。本文也不是Ogre的入门教程,这个请去看Ogre的WIKI

,也有中文版

。(但是WIKI的中级教程有些老,代码一般不能直接在新版本的Ogre和CEGU中工作,但是理解后,稍微进行点改动就行了,中文版可能更老,我没有看,推荐还是去看原版的好,毕竟都是很简单的东西)

 

基础框架了解

在弄清楚一切之前,利用Ogre的iPhone的基础框架,可以得到以下的运行效果:

一个癞蛤蟆一样的Ogre标识模型。。。。。我见过最难看的一个。。。。。。

先在机器上运行一下,了解这个基础框架实现了哪些功能,主要有下列三个:

1.3D场景中显示了天空盒和一个癞蛤蟆皮Ogre头。。。。

2.2D UI中显示了一系列的Debug信息以及OGRE的标识

3.支持触摸进行Camara的旋转。

然后,下面对此框架的了解除了大致框架外,了解3个功能是在哪里实现的。

 

基础框架代码分析

主要部分是OgreFramework.h和OgreFramework.cpp两个文件包含的OgreFramework类,这个类虽然内容简单,但是
基本功能齐全,并且,在此简单的框架的基础上,尽量简单的实现了跨平台,这样的话,可以尝试开发Mac,Win32程序,然后移植到iPhone中,以加
快开发速度,缩短开发周期。

如下所示

#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

class OgreFramework : public Ogre::Singleton<OgreFramework>, OIS::KeyListener, OIS::MultiTouchListener

#else

class OgreFramework : public Ogre::Singleton<OgreFramework>, OIS::KeyListener, OIS::MouseListener

#endif

OgreFramework是一个C++类,并通过Ogre::Singleton做成单件,并且,这里可以看出来,通过OIS的MultiTouchListener来支持多点触摸。

所有的接口如下:

public:

    OgreFramework();

    ~OgreFramework();

#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

    bool initOgre(Ogre::String wndTitle, OIS::KeyListener *pKeyListener = 0, OIS::MultiTouchListener *pMouseListener = 0);

#else

    bool initOgre(Ogre::String wndTitle, OIS::KeyListener *pKeyListener = 0, OIS::MouseListener *pMouseListener = 0);

#endif

    void updateOgre(double timeSinceLastFrame);

    void updateStats();

    void moveCamera();

    void getInput();

    bool isOgreToBeShutDown()const{return m_bShutDownOgre;}  

    bool keyPressed(const OIS::KeyEvent &keyEventRef);

    bool keyReleased(const OIS::KeyEvent &keyEventRef);

#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

    bool touchMoved(const OIS::MultiTouchEvent &evt);

    bool touchPressed(const OIS::MultiTouchEvent &evt);

    bool touchReleased(const OIS::MultiTouchEvent &evt);

    bool touchCancelled(const OIS::MultiTouchEvent &evt);

#else

    bool mouseMoved(const OIS::MouseEvent &evt);

    bool mousePressed(const OIS::MouseEvent &evt, OIS::MouseButtonID id);

    bool mouseReleased(const OIS::MouseEvent &evt, OIS::MouseButtonID id);

#endif

可以从接口看出,此框架设计的使用方式是直接作为变量/成员变量使用,不是希望使用者继承此类。下面分别看各个部分。

初始化

其他部分都属于进一步的操作和更新了,主要部分是Ogre的初始化部分,在initOgre,先看看这个函数:(感觉必要的地方我添加了注释)

 

/|||||||||||||||||||||||||||||||||||||||||||||||

#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

bool OgreFramework::initOgre(Ogre::String wndTitle, OIS::KeyListener *pKeyListener, OIS::MultiTouchListener *pMouseListener)

#else

bool OgreFramework::initOgre(Ogre::String wndTitle, OIS::KeyListener *pKeyListener, OIS::MouseListener *pMouseListener)

#endif

{
// 日志管理的初始化

    new Ogre::LogManager();

    

    m_pLog = Ogre::LogManager::getSingleton().createLog("OgreLogfile.log", true, true, false);

    m_pLog->setDebugOutputEnabled(true);

   
// 不是静态链接的时候使用plugins.cfg配置文件,因为iPhone只能使用静态链接方式,没戏了

    String pluginsPath;

    // only use plugins.cfg if not static

#ifndef OGRE_STATIC_LIB

    pluginsPath = m_ResourcePath + "plugins.cfg";

#endif

    
// Root的创建(Ogre::Root* m_pRoot;)

    m_pRoot = new Ogre::Root(pluginsPath, Ogre::macBundlePath() + "/ogre.cfg");

    

#ifdef OGRE_STATIC_LIB

    m_StaticPluginLoader.load();

#endif

    
// 代码虽然是显示配置对话框的代码,但是示例中不会显示配置对话框,而是直接restore了原来的配置

    if(!m_pRoot->showConfigDialog())

        return false;

   
// 渲染窗口的创建及初始化(Ogre::RenderWindow* m_pRenderWnd;)

    m_pRenderWnd = m_pRoot->initialise(true, wndTitle);

    
// iPhone平台特定操作,设定屏幕位置及大小(这些都是随着设备就固定了的),这里的(0,0)还是2维坐标,即屏幕的左上角
// 有意思的是一开始的时候,Ogre就认为iPhone设备是横的。。。。即RenderWnd height: 320     width: 480
// 即状态OR_LANDSCAPELEFT  = OR_DEGREE_270

#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

  m_pRenderWnd->reposition(0, 0);

  m_pRenderWnd->resize(m_pRenderWnd->getHeight(), m_pRenderWnd->getWidth());

#endif

    
// 以下是SceneMgr,Camera,Viewport的创建及初始化,与一般的过程一样

    m_pSceneMgr = m_pRoot->createSceneManager(ST_GENERIC, "SceneManager");

    m_pSceneMgr->setAmbientLight(Ogre::ColourValue(0.7, 0.7, 0.7));

    

    m_pCamera = m_pSceneMgr->createCamera("Camera");

    m_pCamera->setPosition(Vector3(0, 60, 60));

    m_pCamera->lookAt(Vector3(0,0,0));

    m_pCamera->setNearClipDistance(1);

    

    m_pViewport = m_pRenderWnd->addViewport(m_pCamera);

    m_pViewport->setBackgroundColour(ColourValue(0.8, 0.7, 0.6, 1.0));

    

    m_pCamera->setAspectRatio(Real(m_pViewport->getActualWidth()) / Real(m_pViewport->getActualHeight()));

    

    m_pViewport->setCamera(m_pCamera);

    
// OIS的部分,构造的方式为了跨平台,所以有些独特^^通过字符串的方式来索引参数创建,
// 传递的参数是窗口的句柄,但是也转换成string了

    unsigned long hWnd = 0;

    OIS::ParamList paramList;

    m_pRenderWnd->getCustomAttribute("WINDOW", &hWnd);

    

    paramList.insert(OIS::ParamList::value_type("WINDOW", Ogre::StringConverter::toString(hWnd)));

    

    m_pInputMgr = OIS::InputManager::createInputSystem(paramList);

    
// OIS有MultiTouch的支持,但是在这个框架中还是直接赋值给m_pMouse(这个变量已经根据宏分别创建了)

#if OGRE_PLATFORM != OGRE_PLATFORM_IPHONE

    m_pKeyboard = static_cast<OIS::Keyboard*>(m_pInputMgr->createInputObject(OIS::OISKeyboard, true));

    m_pMouse = static_cast<OIS::Mouse*>(m_pInputMgr->createInputObject(OIS::OISMouse, true));

    

    m_pMouse->getMouseState().height = m_pRenderWnd->getHeight();

    m_pMouse->getMouseState().width     = m_pRenderWnd->getWidth();

#else

    m_pMouse = static_cast<OIS::MultiTouch*>(m_pInputMgr->createInputObject(OIS::OISMultiTouch, true));

#endif

    
// 这里可以参考此类的构造函数,即允许构造此类的时候,传递外来的输入响应对象。

#if OGRE_PLATFORM != OGRE_PLATFORM_IPHONE

    if(pKeyListener == 0)

        m_pKeyboard->setEventCallback(this);

    else

        m_pKeyboard->setEventCallback(pKeyListener);

#endif

    

    if(pMouseListener == 0)

        m_pMouse->setEventCallback(this);

    else

        m_pMouse->setEventCallback(pMouseListener);

    
// 读取配置,与一般的情况一样,只是多了个m_ResourcePath作为基础目录,为Mac和iPhone准备的。
// 这两个平台因为用了Bundle,所以与PC有些不一样

    Ogre::String secName, typeName, archName;

    Ogre::ConfigFile cf;

    cf.load(m_ResourcePath + "resources.cfg");

    

    Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator();

    while (seci.hasMoreElements())

    {

        secName = seci.peekNextKey();

        Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext();

        Ogre::ConfigFile::SettingsMultiMap::iterator i;

        for (i = settings->begin(); i != settings->end(); ++i)

        {

            typeName = i->first;

            archName = i->second;

// 还是为Mac和iPhone进行了一些特殊处理,英文注释很详细了

#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE || OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

            // OS X does not set the working directory relative to the app,

            // In order to make things portable on OS X we need to provide

            // the loading with it's own bundle path location

            if (!Ogre::StringUtil::startsWith(archName, "/", false)) // only adjust relative dirs

                archName = Ogre::String(Ogre::macBundlePath() + "/" + archName);

#endif

            Ogre::ResourceGroupManager::getSingleton().addResourceLocation(archName, typeName, secName);

        }

    }

    Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(5);

    Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();

    
// 创建计时器

    m_pTimer = OGRE_NEW Ogre::Timer();

    m_pTimer->reset();

    // 获取Debug的Overlay层,用于输出一些调试信息

    m_pDebugOverlay = OverlayManager::getSingleton().getByName("Core/DebugOverlay");

    m_pDebugOverlay->show();

    

    m_pRenderWnd->setActive(true);

    

    return true;

}

 

这个函数的目的是很单纯的,作为框架性代码,没有像Ogre的基础教程createScene函数一样,添加场景创建的代码,仅仅是初始化了一些相关的对象。而这个函数已经完成了此基础框架希望完成的大部分功能了。

 

输入

keyPressed,mouseMoved在iPhone中是完全没有什么用了,touch的系列接口虽然与iOS SDK基本一致,但是参数上OIS进行了进一步的分装,估计是为了将来方便移植到其他触摸移动平台(比如Android)。

此参数定义如下:

    //! Touch Event type

    enum MultiTypeEventTypeID

    {

        MT_None = 0, MT_Pressed, MT_Released, MT_Moved, MT_Cancelled

    };

    class _OISExport MultiTouchState

    {

    public:

        MultiTouchState() : width(50), height(50), touchType(MT_None) {};

        /** Represents the height/width of your display area.. used if touch clipping

        or touch grabbed in case of X11 - defaults to 50.. Make sure to set this

        and change when your size changes.. */

        mutable int width, height;

        //! X Axis component

        Axis X;

        //! Y Axis Component

        Axis Y;

        //! Z Axis Component

        Axis Z;

        int touchType;

        inline bool touchIsType( MultiTypeEventTypeID touch ) const

        {

            return ((touchType & ( 1L << touch )) == 0) ? false : true;

        }

       

        //! Clear all the values

        void clear()

        {

            X.clear();

            Y.clear();

            Z.clear();

            touchType = MT_None;

        }

    };

    /** Specialised for multi-touch events */

    class _OISExport MultiTouchEvent : public EventArg

    {

    public:

        MultiTouchEvent( Object *obj, const MultiTouchState &ms ) : EventArg(obj), state(ms) {}

        virtual ~MultiTouchEvent() {}

        //! The state of the touch - including axes

        const MultiTouchState &state;

    };

 

基本上还是很容易理解的,就是有些奇怪的添加了Z坐标,难道将来会有立体触摸?-_-!OIS的作者想的还真远啊。。。。。。。

此外,这种参数封装还是有些弱的,iOS的SDK要强大一些,直接内置了多次触摸的查询等的支持,OIS为了通用,看来是不行了。

 

然后,在touchMoved函数中,实现了camera的旋转。

#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

bool OgreFramework::touchMoved(const OIS::MultiTouchEvent &evt)

{

    OIS::MultiTouchState state = evt.state;

    int origTransX = 0, origTransY = 0;

    switch(m_pCamera->getViewport()->getOrientationMode())

    {

        case Ogre::OR_LANDSCAPELEFT:

            origTransX = state.X.rel;

            origTransY = state.Y.rel;

            state.X.rel = -origTransY;

            state.Y.rel = origTransX;

            break;

            

        case Ogre::OR_LANDSCAPERIGHT:

            origTransX = state.X.rel;

            origTransY = state.Y.rel;

            state.X.rel = origTransY;

            state.Y.rel = origTransX;

            break;

            

            // Portrait doesn't need any change

        case Ogre::OR_PORTRAIT:

        default:

            break;

    }

    m_pCamera->yaw(Degree(state.X.rel * -0.1));

    m_pCamera->pitch(Degree(state.Y.rel * -0.1));

    

    return true;

}

大部分代码是为了不同的设备方向而进行的参数调整,其实主要也就是Camera的yaw,pitch旋转而已,有意思的是,touch参数里面的相对值的存在,使得旋转速度的设定非常简洁。

 

调试信息输出

框架类中剩下的还有点用的东西就是调试信息的输出了,代码如下:

//|||||||||||||||||||||||||||||||||||||||||||||||

void OgreFramework::updateStats()

{

    static String currFps = "Current FPS: ";

    static String avgFps = "Average FPS: ";

    static String bestFps = "Best FPS: ";

    static String worstFps = "Worst FPS: ";

    static String tris = "Triangle Count: ";

    static String batches = "Batch Count: ";

    

    OverlayElement* guiAvg = OverlayManager::getSingleton().getOverlayElement("Core/AverageFps");

    OverlayElement* guiCurr = OverlayManager::getSingleton().getOverlayElement("Core/CurrFps");

    OverlayElement* guiBest = OverlayManager::getSingleton().getOverlayElement("Core/BestFps");

    OverlayElement* guiWorst = OverlayManager::getSingleton().getOverlayElement("Core/WorstFps");

    

    const RenderTarget::FrameStats& stats = m_pRenderWnd->getStatistics();

    guiAvg->setCaption(avgFps + StringConverter::toString(stats.avgFPS));

    guiCurr->setCaption(currFps + StringConverter::toString(stats.lastFPS));

    guiBest->setCaption(bestFps + StringConverter::toString(stats.bestFPS)

                        +" "+StringConverter::toString(stats.bestFrameTime)+" ms");

    guiWorst->setCaption(worstFps + StringConverter::toString(stats.worstFPS)

                         +" "+StringConverter::toString(stats.worstFrameTime)+" ms");

    

    OverlayElement* guiTris = OverlayManager::getSingleton().getOverlayElement("Core/NumTris");

    guiTris->setCaption(tris + StringConverter::toString(stats.triangleCount));

    

    OverlayElement* guiBatches = OverlayManager::getSingleton().getOverlayElement("Core/NumBatches");

    guiBatches->setCaption(batches + StringConverter::toString(stats.batchCount));

    

    OverlayElement* guiDbg = OverlayManager::getSingleton().getOverlayElement("Core/DebugText");

    guiDbg->setCaption("");

}

都是获取到响应的UI元素,然后进行输出,没有太多好讲的。

 

框架驱动代码

上述的框架还不足以构成一个完成的程序,仅仅是跨平台实现中干了一些脏活,可以跨平台的部分,我们还需要实际的驱动代码来使用这个框架。

这些代码又分两个部分,DemoApp类和AppDelegate类。

DemoApp:

class DemoApp : public OIS::KeyListener

{

public:

    DemoApp();

    ~DemoApp();

    void startDemo();

    void setupDemoScene();

    void setShutdown(bool flag) { m_bShutdown = flag; }

    

    bool keyPressed(const OIS::KeyEvent &keyEventRef);

    bool keyReleased(const OIS::KeyEvent &keyEventRef);

private:

    void runDemo();

    Ogre::SceneNode*            m_pCubeNode;

    Ogre::Entity*                m_pCubeEntity;

    bool                        m_bShutdown;

};

 

很简洁也很直接的定义,没有去继承使用Ogre的ExampleApplication等类。仅仅继承了KeyListener

,响应keyPressed

keyReleased

,而在iPhone中我们又不用管。

那么,需要看到就是真实创建天空盒和那个癞蛤蟆头的部分了。

 

void DemoApp::startDemo()

{

    new OgreFramework();

    if(!OgreFramework::getSingletonPtr()->initOgre("DemoApp v1.0", this, 0))

        return;

    

    m_bShutdown = false;

    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Demo initialized!");

    setupDemoScene();

    runDemo();

}

 

此函数完成了前面讲到的OgreFramework的创建及初始化,于是,此时,我们Ogre程序需要的root,scene manager, viewport,camera,等东西都已经有了。

下面直接调用setupDemoScene来创建了场景,然后通过runDemo来运行程序(即进入主循环)。

 

void DemoApp::setupDemoScene()

{

    OgreFramework::getSingletonPtr()->m_pSceneMgr->setSkyBox(true, "Examples/SpaceSkyBox");

    OgreFramework::getSingletonPtr()->m_pSceneMgr->createLight("Light")->setPosition(75,75,75);

    m_pCubeEntity = OgreFramework::getSingletonPtr()->m_pSceneMgr->createEntity("Cube", "ogrehead.mesh");

    m_pCubeNode = OgreFramework::getSingletonPtr()->m_pSceneMgr->getRootSceneNode()->createChildSceneNode("CubeNode");

    m_pCubeNode->attachObject(m_pCubeEntity);

}

 

因为初始化完了,那么创建场景的部分就简单了,如上所示,也就几句代码。属于Ogre常规操作,也就不多说了。

 

//|||||||||||||||||||||||||||||||||||||||||||||||

void DemoApp::runDemo()

{

    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Start main loop...");

    

    double timeSinceLastFrame = 0;

    double startTime = 0;

    OgreFramework::getSingletonPtr()->m_pRenderWnd->resetStatistics();

    

    while(!m_bShutdown && !OgreFramework::getSingletonPtr()->isOgreToBeShutDown())

    {

        if(OgreFramework::getSingletonPtr()->m_pRenderWnd->isClosed())m_bShutdown = true;

#if OGRE_PLATFORM != OGRE_PLATFORM_IPHONE

        Ogre::WindowEventUtilities::messagePump();

#endif    

        if(OgreFramework::getSingletonPtr()->m_pRenderWnd->isActive())

        {

            startTime = OgreFramework::getSingletonPtr()->m_pTimer->getMillisecondsCPU();

                    

#if OGRE_PLATFORM != OGRE_PLATFORM_IPHONE

            OgreFramework::getSingletonPtr()->m_pKeyboard->capture();

#endif

            OgreFramework::getSingletonPtr()->m_pMouse->capture();

            OgreFramework::getSingletonPtr()->updateOgre(timeSinceLastFrame);

            OgreFramework::getSingletonPtr()->m_pRoot->renderOneFrame();

        

            timeSinceLastFrame = OgreFramework::getSingletonPtr()->m_pTimer->getMillisecondsCPU() - startTime;

        }

        else

        {

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32

           Sleep(1000);

#elif OGRE_PLATFORM == OGRE_PLATFORM_APPLE

           sleep(1000);

#endif

        }

    }

    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Main loop quit");

    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Shutdown OGRE...");

}

看完这里,代码上是没有什么疑问了,主循环嘛。。。。。。。。不过,对于代码实现的问题上,还是有些问题的,从前面的代码来看,主循环通过

    while(!m_bShutdown && !OgreFramework::getSingletonPtr()->isOgreToBeShutDown())

控制,而且里面完全没有帧率控制代码。。。。也没有使用Ogre在教程中提倡的使用FrameListeners的frameRenderingQueued

回调函数来完成update,这个感觉比较奇怪。

而假如是通过

if(OgreFramework::getSingletonPtr()->m_pRenderWnd->isActive())

即,是否Actice来控制帧率也不正常,这个应该是窗口在后台不需要处理输入的时候使用的,而且一次sleep了1秒钟,也不可能胜任这个工作。

带着疑问调试此函数的代码,发现根本没有调用此函数。。。。。。-_-!

这里搞这么多#if OGRE_PLATFORM != OGRE_PLATFORM_IPHONE,结果iPhone中根本不调用,还是比较具有迷惑性的。。。。。。

 

真实的版本,当然就看AppDelegate了,AppDelegate是ObjC类。。。。。。看到这里,突然感觉,我虽然仅仅是最近用了几个月的ObjC,但是因为最近一直在用Objc,竟然感觉比用了好几年的C++更加亲切了。。。。。-_-!

 

AppDelegate

在main函数中,首先看到AppDelegate的使用:

int main(int argc, char **argv)

#endif

{

#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

  int retVal = UIApplicationMain(argc, argv, @"UIApplication", @"AppDelegate");

  [pool release];

  return retVal;

#else

}


以,我原本以为AppDelegate就是一个从ObjC到DemoApp的一个小外壳而已,处理一些简单的delegate回调,结果发现根本不是这
样,在iPhone版本的示例程序中,AppDelegate才是实际的DemoApp,而原来DemoApp,其实AppDelegate只是使用了其
setup场景的部分而已。原来我也是只猜到了开始,但是猜不到这个结果。。。。。。。

在applicationDidFinishLaunching中经过了一些在iPhone中必须进行的一些window,view操作后,直接进入了主题,go函数,看看go函数:

- (void)go {

  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

  try {

    new OgreFramework();

    if(!OgreFramework::getSingletonPtr()->initOgre("DemoApp v1.0", &demo, 0))

      return;

    

    demo.setShutdown(false);

    

    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Demo initialized!");

    

    demo.setupDemoScene();

    OgreFramework::getSingletonPtr()->m_pRenderWnd->resetStatistics();

    

    if (mDisplayLinkSupported)

    {

     
// CADisplayLink is API new to iPhone SDK 3.1. Compiling against
earlier versions will result in a warning, but can be dismissed

     
// if the system version runtime check for CADisplayLink exists in
-initWithCoder:. The runtime check ensures this code will

      // not be called in system versions earlier than 3.1.

      mDate = [[NSDate alloc] init];

      mLastFrameTime = -[mDate timeIntervalSinceNow];

      

      mDisplayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self selector:@selector(renderOneFrame:)];

      [mDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

    }

    else

    {

      mTimer = [NSTimer scheduledTimerWithTimeInterval:(NSTimeInterval)(1.0f / 60.0f) * mLastFrameTime

                                                target:self

                                              selector:@selector(renderOneFrame:)

                                              userInfo:nil

                                               repeats:YES];

    }

  } catch( Ogre::Exception& e ) {

    std::cerr << "An exception has occurred: " <<

    e.getFullDescription().c_str() << std::endl;

  }

 

  [pool release];

}

 

原来这才是真正驱动OgreFramework的地方啊。。。。。。前面那都是迷惑人的。。。。

 

    new OgreFramework();

    if(!OgreFramework::getSingletonPtr()->initOgre("DemoApp v1.0", &demo, 0))

      return;

    

    demo.setShutdown(false);

    

    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Demo initialized!");

    

    demo.setupDemoScene();

 

在iPhone版本中,在go函数中进行了OgreFramework的创建及初始化,然后调用demo的setupDemoScene进行场景的创建(前面分析的也就这一部分是对的了。。。。。。其他demo部分分析仅适用于其它版本)。

 

    if (mDisplayLinkSupported)

    {

     
// CADisplayLink is API new to iPhone SDK 3.1. Compiling against
earlier versions will result in a warning, but can be dismissed

     
// if the system version runtime check for CADisplayLink exists in
-initWithCoder:. The runtime check ensures this code will

      // not be called in system versions earlier than 3.1.

      mDate = [[NSDate alloc] init];

      mLastFrameTime = -[mDate timeIntervalSinceNow];

      

      mDisplayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self selector:@selector(renderOneFrame:)];

      [mDisplayLink setFrameInterval:mLastFrameTime];

      [mDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

    }

 

可以看到此处是通过iPhone的CADisplayLink类来完成刷新及帧率控制的,(前面还特意检查了版本,以判断当前iPhone版本是否支持此特性,3.1以后才有的东西)而renderOneFrame里面的内容很简单,这里就不讲了。

 

看看CADisplayLink吧,在apple的文档中,有如下描述:

A
CADisplayLink object is a timer object that allows your application to
synchronize its drawing to the refresh rate of the display.

很明显,这个iPhone平台特定的东西估计要比Ogre跨平台的FrameListener要好用一些,所以此基础框架程序中使用了这个iPhone原生的东西。

不过这个代码有个问题:

      mDate = [[NSDate alloc] init];

      mLastFrameTime = -[mDate timeIntervalSinceNow];

      [mDisplayLink setFrameInterval:mLastFrameTime];

 

此时mLastFrameTime一般是个远小于1的变量,而在apple的文档中有如下描述:

frameInterval

The number of frames that must pass before the display link notifies the target again.

@property(nonatomic) NSInteger frameInterval

Discussion

The
default value is 1, which results in your application being notified at
the refresh rate of the display. If the value is set to a value larger
than 1, the display link notifies your application at a fraction of the
native refresh rate. For example, setting the interval to 2 causes the
display link to fire every other frame, providing half the frame rate.

Setting this value to less than 1 results in undefined behavior and is a programmer error.

 


意最后一句。。。。。。设置成小于1的值会导致未定义行为,并且是一个错误。。。。。既然默认是1,那么其实后面的frameInterval根本不用设
置。(原程序能够正常的运行,估计apple还是进行了一定的错误处理,当小于1时还是设置成1了)为了安全起见,这一句还是删掉吧。事实上,删掉后,运
行还是正常,此时使用默认值1,也就是每次显示刷新调用一次此函数。

 

相对的,看Cocos2D for iPhone的代码,现在默认的director类CCDisplayLinkDirector,对DisplayLink的使用实现代码如下:

    int frameInterval = (int) floor(animationInterval_ * 60.0f);

    

    CCLOG(@"cocos2d: Frame interval: %d", frameInterval);

    displayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self selector:@selector(preMainLoop:)];

    [displayLink setFrameInterval:frameInterval];

    [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

 


意,这里的animationInterval是秒为单位的表示时间间隔的变量,但是这里没有直接使用此变量来设定frameInterval,而是乘以
了60,(Cocos认为iPhone设备的刷新率近似为60)应该来说,这才是正确用法。。。。。。。我看来可以向Ogre的开发组提交个bug
了。。。。。。

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

从源码编译CEGUI for OGRE 的配置

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

   

    现在在国内做游戏,似乎怎么都绕不开OGRE和CEGUI的学习,因为他们实在是太流行了。。。。。OGRE在Google中搜索game engine长期排在第一,而CEGUI又几乎是OGRE的官方UI。。。毕竟不是盖的。我第一份工作的时候就做过一些CEGUI相关的工作,(但是那时候引擎不是OGRE)但是一直没有太深入的学习,然后在游戏开发的路上绕了很大一圈,接触了OpenGL(ES),以及各色2D,3D引擎,最后似乎还是回到了OGRE和CEGUI,所以还是有些感慨。。。。。。

    当自己需要从头开始做某些基础工作的时候,与拿着成熟的框架和工程感觉还是有些不一样的。比如CEGUI和OGRE的配置。。。。。。。

    主要参考资料来自于《Building CEGUI for Ogre / OgreRenderer
》。

    现在(2010.9.13)CEGUI的最新版是0.72,到这里下载
。(源码版本)

    然后,将CEGUI解压到某个地方。我这里选择的是OGRE的目录下。

此时,CEGUI-0.7.2/projects/premake中可以看到批处理build_vs2008.bat,运行一下就可以得到想要的VS2008工程,第一次我尝试的时候编译此工程,然后拷贝相关的lib,dll到OGRE的相关目录,会发现还是少一个文件,debug版本是CEGUIOgreRenderer_d.lib,然后我发现还需要配置。这里感觉就没有那么直观了。。。。。。。。。不属于work out of box,这也是本文写作的唯一有效目的。。。。。

配置:

1。在批处理的目录下的config.lua文件中,修改如下两行内容:

OGRE_PATHS = { "..", "include/OGRE", "lib" }

OIS_PATHS = { "..", "include/OIS", "lib" }

这里第一个字符串的路径根据你将CEGUI解压的地方来配置,我这里由于CEGUI已经解压到OGRE的目录下,所以仅仅使用父目录就可以了。

2。在config.lua中,将OGRE_RENDERER那一行设为true,如下:

OGRE_RENDERER = true

3。在新版的OGRE中增加了对boost的依赖,所以还需要配置一下boost。(修改CEGUI_EXTRA_PATHS的内容)

我这里是:

CEGUI_EXTRA_PATHS = {

     { "../boost_1_42", "", "lib", "CEGUIOgreRenderer" },

}

此时会发现CEGUI的solution中会多出一个CEGUIOgreRenderer的工程。并且,经过前面的配置,路径都已经配置好了。

这里有些惊喜的是,发现了irrlicht相关的东西,呵呵,将来也可以尝试,个人对irrlicht的简单易用也印象深刻,不知道在学了OGRE后,下次还有没有机会再次使用。

然后,编译就好了,会报一堆的警告。。。。。。。。。。。。。感觉这些代码写的都有问题。。。。。在公司的代码里面,经常是一句warning都不能有的,而且这些waring都是类型转换警告。。。。估计在某些时候肯定会有问题。。。暂时不管了。

事实上,最后还会有个错误,

LINK : fatal error LNK1104: cannot open file 'OgreMain_d.lib'

因为配置的时候好像不能分debug/release来配置,(前面的文档没有讲这一点,我也没有深究了。。。。也许有办法吧)而OGRE的lib目录事实上又分debug/release子目录的。。。。。所以,其实看起来蛮自动化的配置,最后还是少不了手动干预一下。

手动修改lib目录后,问题解决。

此时将编译出来的lib,dll都拷贝到OGRE的相关目录,(因为我不准备修改CEGUI,所以简单的就拷贝了,需要修改CEGUI的,可以直接修改CEGUI工程配置,设置为编译后拷贝到相应目录)就可以直接在OGRE中使用CEGUI了,只需要再配置工程的CEGUI include目录就好。

然后,当遇到过这么多坑以后,满以为总该顺利了。。。。。。事实上,还有一个大坑在前面等着你,在最新的CEGUI版本中,你会遇到“应用程序正常初始化(0xc0150002)失败”错误,而且不会给你任何头绪。。。。。其实我费了这么多劲,非要从源码编译CEGUI和OGRE,而不是使用各自的SDK,就是因为使用SDK的时候碰到这个问题了,从经验判断应该是库的编译版本不匹配的问题,结果我自己将所有的源码都编译了一次了,还是有问题。。。。。。。。。无奈之余,在网上搜索了一下,碰到这个问题的人还不是少数。

这个哥们描述的背后的故事
。。。。。还提供了hack解决方案,牛,可惜我是用VS2008的,VS2005那个补丁不适合我,运行安装不了,我也还是希望通过正常的补丁途径解决。而这个哥们提供了完善的解决方案
。基本上,简单的说,VS2008的解决方案就是下载正确的依赖文件VC90
那个,或者直接下CEGUI SDK VS2008的SDK
,只是千万不要下VC80任何相关的东西。。。。(我很郁闷MS竟然不让VS2008在这种程度上支持VS2005,起码也能够让任何VS2005编译的东西在VS2008得到直接的支持啊)然后为自己的VS2008打上SP1补丁,就好了。这个问题真的折腾了我挺久,希望大家不要再继续被折腾了。。。。。。。。。。。。。

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....