August 15, 2009 - 7:26 pm
Tags: 3d, terrain, 图形学, 地形生成
Posted in 程序设计 | No comments
地形生成是一个很有意思的话题,通过简单的函数以及一些参数配置,就可以轻松的生成接近于自然地貌的地形。
两种众所周知的地形生成算法分别是
1、 Fault Formation
2、 Midponit displacement (又名diamond square)
Fault Formation 算法
这个算法的本质其实很简单。想象一个方块,在这个方块中随机取一个点为起始点,再随机选择一个方向,形成一条射线,然后将高度图中这个射线左侧(或者右侧)的部分全部添加一个Delta。重复上述步骤若干次,每次将添加的Delta减小一些,一个简单的高度图就可以生成出来了。
Delta应当与当前迭代次数与总迭代次数的比值成线性关系。
Delta = MaxHeight – (MaxHeight – MinHeight) * CurrentItr / TotalItr;
这样第一次迭代所产生的影响最大,而后的迭代中对整个高度图的影响依次递减,在之前的高度基础上继续增加高度图中的细节。通常来说,迭代次数越多,效果会越好。
实现FaultFormation的伪码如下:
Do_Formation(float* pfBuffer, int iLen, Point ptStart, Point ptEnd, float fDelta)
{
Point ptDir = ptEnd – ptStart;
For (int z = 0; z < iLen; ++z)
For (int x = 0; x < iLen; ++x)
{
Point ptDirCur [...]
May 8, 2009 - 11:15 pm
Tags: Ogre, Plugin, 软件设计
Posted in C++ & Design, 程序设计 | No comments
Ogre是一个跨操作系统平台的开源3D引擎,既支持DirectX,也支持使用OpenGL,支持可替换的场景管理算法(BSP, OCT)。为Ogre提供这些灵活可扩展性的基础之一就是其面向插件的设计。
很多常用的软件大都提供了插件接口,用以扩展应用程序设计者最初未想到的功能,比较常见的譬如PhotoShop的滤镜,After Effect中的各种插件(最有名的比如shine),3dMax的插件譬如渲染器,魔兽世界的辅助插件等等。
通常,插件本身通常也需要实现主应用程序所需要的必要接口,从而使得插件可以被应用程序加载执行。此外,插件的实现也需要由主应用程序提供一些接口api,通过这些接口,插件可以对主应用程序的功能进行调用。
插件可以是动态链接库(win32平台上为DLL文件),也可以是以脚本的形式提供的,比如魔兽世界中的插件就是使用lua编写的,插件也可能是某种应用程序自定义的文件,只要该应用程序提供了创建该类文件的方法并实现解析、执行功能即可。(不同的实现形式各有利弊,具体需要参考插件及应用程序所处的运行环境进行取舍)
采用插件的一个巨大的好处,以及很多应用程序中使用插件的主要目的就是,我们可以在不需要改动应用程序本身的情况下扩展应用程序的功能。
在Ogre中,插件被用来提供渲染子系统(RenderSystem),不同类别的图形API被封装在不同的渲染子系统的视线当中,Ogre默认提供了DX和OpenGL的实现,甚至,如果我们乐意,甚至可以只用绘点函数实现一套纯软件的渲染子系统提供给Ogre使用。以此为例,用简单的形式来展示这种实现大概类似于下面这样:
class RenderSystem
{
// … operations that a render system need to support
};
// In DX_RenderSystem.dll (in plugin dx rendersystem )
class DX_RenderSystem : public RenderSystem
{
// … implementation & override of the operations from RenderSystem using DirectX
};
// In GL_RenderSystem.dll (in plugin openGL rendersystem)
class GL_RenderSystem : public RenderSystem
{
// … implementation & override of the operations [...]
January 18, 2009 - 6:06 pm
Tags: Ogre, Singleton, 设计模式
Posted in C++ & Design, 程序设计 | No comments
凡是使用C++进行开发的人,大都或是了解,或是直接使用过Singleton模式,但是Singleton的多种实现方式有什么差异?不同的实现细节背后究竟蕴含着什么意义?本文试图列举常见的几种不同的Singleton实现方式,考察这些不同的实现方式中的细节差异,并剖析其好处与缺点,试图对Singleton的实现方式做一个较为完整的总结。
我们在使用C++编写实际项目的时候,往往会有对全局对象/变量的访问需求。对全局变量的不加封装的访问,会导致许多麻烦的问题:譬如在调试中,由于对全局变量的修改被分散在程序的各个角落里,使得我们无法准确判断一个错误是在何时发生的;再比如,在多线程环境下,如果多个线程都需要访问一个变量,则需要做线程同步操作,在没有良好的封装的情况下,这种状况可能会导致完全莫名的coredump。不过本文并不是想说Singleton有什么好处或者有什么作用,仅仅只是想总结Singleton的不同实现方式之间的异同,以及适用场合。
December 21, 2008 - 10:06 pm
Tags: C++, Listener, Ogre, 泛型回调
Posted in C++ & Design, 程序设计 | 1 comment
关于低耦合的消息传递,实现的方式有很多,哪种方法更好与具体的使用环境有关,本文使用试错的方法,逐步探索达成这一目的具体方式,并理解实现方式背后的原因。
面向对象的系统当中,不可避免的有大量的类间消息传递的需求:一个类需要通知另一个或几个类做些什么。
这种类间消息传递,简单的说,就是调用其他类的方法。
如下:
void A::OnMessageXX()
{
B::GetInstance()->DoSomething();
}
在这里,类A需要通知类B做些事情。这种调用在所有的面向对象程序中都是极其常见的。
但是如果类A需要调用类B,就不可避免的产生了耦合性。虽然耦合性终归是不可能完全避免的,但是在一定程度上降低耦合性是完全可能的。
(至于为什么在设计中应该尽可能降低耦合性,不在本文的探讨范围之内)
上面的例子,我们使用了Singleton的模式,从全局作用域中获取了B的实例,并调用了B的相关方法。使用Singleton的一个缺点是,假若我们希望对类A编写测试代码,我们需要做一些额外的解耦合工作。(关于编写测试与解耦合,可以参考Robert C. Martin Series的Working Effectively with Legacy Code一书,该书的中译版在这)
我们也可以通过将B参数化的方法降低A与B间的耦合程度,像下面这样:
void A::OnMessageXX(B* pBInstance)
{
pBInstance->DoSomething();
}
现在的写法要比之前的做法耦合性低,通过使用多态的方法,现在传入函数的类B指针可能是另一个实现了B的相应接口的派生类,A并不关心B接口背后的具体实现。
但是等等,你说,现在对类B的耦合性虽然在A中被降低了,但是依旧存在于调用A::OnMessageXX的地方。在那里我们还是需要取得B的实例,然后传递给A。
没错,是这样。
通过参数化类A的方法,我们把类A与类B间的耦合转移了一部分到A的调用者那里。实际上总的耦合并没有消除,只是被分解了。但是程序设计中不可能完全不存在耦合,我们需要做的是”正确”,而不是”完美”。类A的耦合性降低了,使得我们在未来需求变更的时候,类A有更大的可能性不需要被修改,并且对功能的扩展更加友好,这就达成了我们的目标了。
基于上述做法,如果我们在未来扩展是派生出一个B的子类,override相关的方法,那么类A的代码基本是不需要修改的。
不过,问题是,假若A::OnMessageXX中,并不仅仅需要对类B发出消息,还需要对一系列相关的类B1,B2,B3等等发出消息呢?
哦,或许我们可以这样做:
void A::OnMessageXX(const std::list<B*>& lstBInstances)
{
for (std::list<B*>::const_iterator itr = lstBInstances.begin();
itr != lstBInstances.end();
++itr)
{
(*itr)->DoSomething();
}
}
是的,上面这是一种做法,有一系列B的对象需要被通知到,所以我们可以用一个列表把他们串起来,然后在循环中通知他们去干活。不过这样做的前提是,这一系列B对象都是派生自一个公共基类B,有共通的接口;此外,我们需要在A的OnMessageXX被调用之前构造一个需要接受通知的B对象列表。
当A需要通知B,C,D等一系列没有公共接口的对象的时候,上面的这种做法就无法处理了。
对于B、C、D等需要由A来调用的类来说,它们需要在A通知它们的时候,做一些特定的事情。而又A则是在某些特定的时刻需要通知B、C、D。这样,我们可以把问题看成一个消息响应机制。
B、C、D可以在A的某些事件上注册一些回调函数,当事件发生时,A确保注册该事件的函数被调用到。
如下:
typedef void(callback*)();
class A {
public:
enum EventIds {
EVENT_MSG1,
EVENT_MSG2,
};
void RegisterEvent(int nEventId, callback pfn);
private:
callback m_pfnCallback;
};
现在,B可以调用A::RegisterEvent注册一个事件,并传递一个函数指针给A。
当A中发生了注册的事件时,这个函数指针会被回调到。
不过这种简单的做法适应性很差:
1、 不能支持单个事件的多个callback (可能有很多类都需要注册该事件,并在事件发生时依次被回调)
2、 不能支持多个事件的同时存在
3、 回调函数没有参数’
针对问题1,2,我们可以使用一个事件映射解决问题,做法如下:
typedef int EventId;
typedef void (callback*)();
typedef std::list<callback> CallbackList;
typedef std::map<EventId, CallbackList> CallbackMap;
现在这个数据结构就能够支持多个event同时存在,且每个event都可以支持多个回调函数了。
但是这种用法依旧很不方便,如果类B想要注册A上的一个事件,他需要定义一个 callback类型的函数,并把这个函数的地址传递给A。问题是,往往我们希望类B的回调函数在被调用到的时候,对类B中的数据和状态进行修改,而一个单独的函数,若想获得/修改B中的状态,则必须要与类B紧密耦合。(通过获取全局对象,或者Singleton的方式)
这种紧密耦合引发我们的思考,能否在Callback中同时包含类B的指针与类B的成员函数。
答案是肯定的:泛型回调就可以做到这一点。关于泛型回调(Generic callback)的信息,在Herb [...]
December 21, 2008 - 12:59 pm
Tags: C++, Ogre, 模板
Posted in C++ & Design, 程序设计 | 2 comments
有些时候我们可能想做这样一件事:
float f = 1.f;
int n = 2;
std::vector<X> myContainer; // X是一个虚构的用户定义类型
myContainer.pushback(X(f));
myContainer.pushback(X(n));
我们想在一个容器里保存两种乃至多种不同的数据类型。
但是,显然普通的模板参数如std::vector<int>,或者std::vector<float>都无法满足我们的需求。
也可能存在下面这样的情况:
float fVal = 1.f;
X xMsg = fVal;
PushMessage(xMsg); // PushMessage发送了一个异步消息
// 另一个地方(可能是另一个线程),回调函数被调用:
OnMessageXXX(X xMsg)
{
float f = static_cast<float>(xMsg);
// do something with value f
}
针对这种需求,我们想要一种类型,它可以接受任意的类型,并在需要的时候把我们放入的真正的类型取出来,它可以被放置到容器中,可以被拷贝,以及串行化(前提要求被置入其中的类型可以被输出到标准输出流–std::ostream当中)。
如何设计这种类型呢?
一种容易想到的方案如下:
class Any {
public:
enum {
ANYTYPE_INT,
ANYTYPE_FLOAT,
ANYTYPE_LONG,
// … other data types
};
union {
int nIntData;
float fFloatData;
long lLongData;
// … other [...]