this is a extra element for clear the floated element
分享——Nebula引擎原理技术讲解
  • 12/31
  • 2008
程序设计 | 游戏开发 1826 次查看
  Nebula是德国一套开源的3d引擎,具体怎样这里不作评价,相关资料可从网上搜索.

  官方网站参见: http://www.radonlabs.de

  使用Nebula也算蛮久了, 为了给网站充数点内容,现在来说说它所用到的一些c++相关技术

  如何实现一套与脚本紧密联系的对象管理系统(将在后面给出自己仿照nebula而写的完整可运行的程序):

  一.基本的思想:

  1. 程序中所产生的对象用类似于windows操作系统中的目录结构来管理,

  也就是每个对象是一个结点(有对应的名称),每个结点下可以有多个子结点(也有各自对应的名称).如此循环...

  2. 程序中有了以上的"目录结构式的对象管理系统", 脚本可通过与程序结合来实现在脚本中调用相应方法来创建

  各目录及对象,并可在脚本中通过相应的目录结构名称来访问每一个对象,以及在各对象之间传递消息.当然也可以通过目录

  结构名来删除对象(包括其目录下的子对象也会自动删除).

  3. 有了以上的一切就有了一套与脚本紧密联系的对象管理系统.下面给出一个使用tcl脚本来撰写的例子参考如下

  (这个例子只供参考,具体可实际编译运行通过的例子将在后面详解后给出):

  #--------------------------------------------------------------------------

  # 创建:

  new

  nd3d8server

  /sys/servers/gfx

  # 访问:

  sel /sys/servers/gfx

  .setdisplaymode

  "-type(win)-w(800)-h(600)"

  .setclearcolor

  0.0 0.0 0.0 0

  .setperspective

  45 1.33 1.0 10000.0

  sel ..

  # 删除:

  delete /sys/servers/gfx

  #--------------------------------------------------------------------------

  [说明:]

  前面带"#"号的行表示注释.

  new 是nebula实现的方法, 可以根据给出的字符串参数产生对应的类型对象

  sel 是nebula实现的方法, 可以根据给出的字符串参数选择对应的类型对象, 当参数为".."时表示退到上一层目录

  二.具体实现:

  首先我们分析要实现如上的脚本目录对象系统所需要的技术:

  1. 程序中要实现目录结构的对象管理系统.

  2. 要有一套脚本系统,这可以用现成开源的.这里我们选择nebula习惯所用的tcl(当然你可以选择其它)

  3. 程序与脚本结合(交互).

  下面我们先分别讨论一下nebula是如何如何实现上述各点的:

  --> 1.先看如何实现第1点:

  a. 首先nebula有一个叫nRoot的类,它是所有对象类的基类,nRoot中有一个名字变量,有一个parent指针指向它的父结点,

  还有一个childlist链表保存它所拥有的子结点.因为结点也是nRoot类,所有结点的子结点中也可能有子结点,

  这样就形成了一个树形目录结构.nRoot中有一个nRoot* Find( const char* name )方法,功能很简单,就是遍历自已的

  childlist链表对比每一个nRoot结点对象的名字,如果相同就返回些对象的指针.

  b. 然后nebula还有一个叫nKernelServer的核心管理类,

  1) 一开始nKernelServer就产生了一个全局可见的单件对象(单件就是只有一件,也就是全局唯一,你可以把它想象成一个全局的静态对象)

  2) 这个nKernelServer对象有一个根据类名字符串来创建对象的方法: CheckCreatePath( const char* szClassName, const char *szPath );

  第一个参数是类名字符串,第2个参数就是目录对象名.例如你有一个图形管理类:nGfxServer,你可以创建它的一个对象并起名为"/sys/servers/gfx",

  如: ks->CheckCreatePath( "nGfxServer", "/sys/servers/gfx" );[注:ks即nKernelServer的单件对象]因为传的是类名字符串,所以从脚本中写下:

  new nGfxServer /sys/servers/gfx

  这样的语句时,tcl会把"nGfxServer"字符串返回给nebula的tclcmd_New函数(这个函数是nebula在初始tcl时注册告知tcl的),

  然后tclcmd_New通过调用单件ks的CheckCreatePath( "nGfxServer", "/sys/servers/gfx" );方法来创建了这么一个对象.

  3) 另外nKernelServer中有一个nRoot* Lookup( const char* szPath )的函数, 功能是你传入目录对象名,它会查到并返回对应对象指针.

  4) 当然nKernelServer中还有一个nRoot *cwd变量, 它是用来指向当前目录(或是对象)的,例如,你在脚本中写下:

  sel /sys/servers/gfx

  这样的语句时,tcl分析到sel时就调用nebula告诉它的tclcmd_Sel函数(如何注册函数告知tcl这是我们在后面第2点的内容), 而tclcmd_Sel函数

  会调用上述所说ks->Lookup函数来找到"/sys/servers/gfx"对象的地址,并把它赋给nKernelServer的cwd变量,以后,写下:

  .setdisplaymode

  "-type(win)-w(800)-h(600)"

  .setclearcolor

  0.0 0.0 0.0 0

  .setperspective

  45 1.33 1.0 10000.0

  [注意:前面带"."号]

  这时,tcl脚本没有这样的用法,于是它就把这些字符串传给一个叫tclcmd_Unknown的函数,nebula在其中分析字符串,把"."与后面的字符串一一分开,

  并把"."后的第一个字符串(如:"setdisplaymode")作为nKernelServer的cwd所指向的对象的方法来调用,并传给它接着后面的参数

  (如:"-type(win)-w(800)-h(600)").当然要让cwd对象把"setdisplaymode"字符串当成它的函数来调用是比较麻烦的.nebula是如何实现的呢?

  我们将在后面第3点祥细说明:), 说了这么多,现在还是让我们来看看一个实现上述目录对象管理系统的代码:

  [注: 这是我仿nebula写的简单程序,不考虑效率等问题,只作说明用]

  // tnd.cpp : Defines the entry point for the console application.

  //

  #include

  #include

  #include

  #pragma warning(disable: 4786)

  using namespace std;

  #ifndef MAX_PATH

  # define MAX_PATH 1024

  #endif

  //----------------------------------------------------------------

  class nRoot

  {

  string

  m_strName;

  nRoot

  *m_pParent;

  vector m_vecChild;

  public:

  nRoot(const char *szName) : m_strName(szName), m_pParent(0){}

  nRoot* Find( const char *szName ){

  for( unsigned int i=0; i
  nRoot *pRoot = m_vecChild[i];

  if( 0 == pRoot->m_strName.compare( szName ) ){

  return m_vecChild[i];

  }

  }

  return 0;

  }

  void SetName( const char *szName ){

  m_strName = szName;

  }

  void AddChild( nRoot *pChild ){

  m_vecChild.push_back( pChild );

  }

  };

  //----------------------------------------------------------------

  class nGfxServer : public nRoot

  {

  public:

  nGfxServer(const char *szName) : nRoot(szName){}

  void Test(){

  __asm int 3 // 这里是测试看是否到这里中断(vc下支持的写法,其它编译器请用类似std::cout方法代替)

  }

  };

  //----------------------------------------------------------------

  class nKernelServer

  {

  nRoot *m_pRoot;

  nRoot *m_pCWD;

  nRoot* NewObject( const char *szClassName, const char *szName );

  public:

  nKernelServer(){

  this->m_pRoot = new nRoot("/");

  this->m_pCWD = this->m_pRoot;

  }

  nRoot* CheckCreatePath( const char* szClassName, const char *szPath );

  nRoot* Lookup( const char* szPath );

  void SetCwd( nRoot *o ){

  this->m_pCWD = o ? o : this->m_pRoot;

  }

  };

  static nKernelServer g_ks;

  //----------------------------------------------------------------

  //----------------------------------------------------------------

  nRoot* nKernelServer::CheckCreatePath( const char* szClassName, const char *szPath )

  {

  nRoot* parent = 0;

  nRoot* child

  = 0;

  if( '/' == szPath[0] ){ // AbsolutePath

  parent = this->m_pRoot;

  }else{

  parent = this->m_pCWD;

  }

  char strBuf[MAX_PATH];

  strcpy(strBuf, szPath);

  char *pNextPathComponent = NULL;

  char *pCurrPathComponent = strtok(strBuf, "/");

  if( pCurrPathComponent )

  {

  // for each directory path component

  while( (pNextPathComponent = strtok(NULL, "/") ))

  {

  child = parent->Find( pCurrPathComponent );

  if (!child)

  {

  child = new nRoot( pCurrPathComponent );

  parent->AddChild( child );

  }

  parent = child;

  pCurrPathComponent = pNextPathComponent;

  }

  }

  // curPathComponent is now name of last path component

  child = parent->Find( pCurrPathComponent );

  if (!child)

  {

  child = (nRoot*) this->NewObject( szClassName, pCurrPathComponent );