摘要:
本文阐述了如何利用
C++
的
Template
开发出方便组装,适合各种应用的 泛型粒子系统,开发过程中使用到了
Boost.Mpl
和
Boost.Random
。
关键字:
粒子系统,泛型编程,
Boost
〇、引言
在各种
3D
应用中,粒子系统扮演着举足轻重的角色。雨雪的模拟、流水的模拟、火焰的模拟等等都离不开粒子系统。传统的粒子系统的开发很难同时满足各种各样的需要,只能针对特定的应用做特定的开发,有没有什么方法可以开发出适合各种应用的粒子系统呢?这让我们想到了求助
C++
的
Template
。
一、
粒子结构
在传统的粒子系统中我们常看到这样的粒子结构的定义。
struct
SParticle {
tVector3
m_vPos;
tVector3
m_vVel;
tColor
m_cColor;
tReal
m_rFade;
tReal
m_rLife;
};
|
这样做的缺点就是整个粒子的结构被固定死了,无法适应不同的需要,针对不同类型的粒子就必须定义不同的结构。为了解决这个问题我们把粒子拆分成不同的部分,这样就可以按需要进行组装,并且针对粒子的特定部分制定的算法可以得到充分的复用。我们可以做如下拆分:
//
粒子位置
struct
SParticlePos {
tVector3
m_vPos;
};
//
粒子速度
struct
SParticleVel {
tVector3
m_vVel;
};
//
粒子颜色
struct
SParticleColor {
tColor
m_cColor;
};
//
粒子退色因子
struct
SParticleFade {
tReal
m_rFade;
};
//
粒子寿命
struct
SParticleLife {
tReal
m_rLife;
};
|
把粒子拆分以后我就需要一个组装机制。
//
粒子组装器
template
<
class
_PartIter,
class
_PartsVector
> classTParticlePartHolder
: publicboost::mpl::deref< _PartIter >::type,
, publicTParticlePartHolder< _PartIter::next >
{};
template
<
class
_PartsVector
> classTParticlePartHolder< boost::mpl::end< _PartsVector >::type, _PartsVector >
: publicboost::blank
{};
|
这里用到了
Boost
的
mpl
库中的类型序列容器作为粒子组装器的
_PartsVector
模板参数,它可以是
boost::mpl::vector
或其它与之兼容的类型序列容器,用于容纳粒子个各个不同部分。
_PartsIter
模板参数是
_PartsVector
的一个跌代器,它指向了位于
_PartsVector
中的一个粒子部分。
TParticlePartHolder
是一个自递归继承的模板类,递归结束条件由一个偏特化的
TParticlePartHolder
确定。粒子组装器每一次递归继承一个
_PartIter
所指向的粒子部分(通过
boost::mpl::deref<>
获取),并把
_PartIter
向后移动一次传入下一次递归,直到
_PartIter
指向了
_PartsVector
容器的末端(继承一个空类
boost::blank
)。
有了粒子组装器我们还需要一个名为
TParticlePolicy
的模板类把它包装起来。
//
粒子
template
<
class
_PartsVector
> structTParticlePolicy
: publicTParticlePartHolder< boost::mpl::begin< _PartsVector >::type, _PartsVector >
{
typedef
_PartsVector
tPartsVector;
//
部分数
static
const
int
s_ciNumOfParts = boost::mpl::size< _PartsVector >::type::value;
}
|
TPaticlePolicy
接受一个名为
_PartsVector
的类型序列容器,并以指向
_PartsVector
首的迭代器(通过
boost::mpl::begine<>
获取)和
_PartsVector
作为参数继承
TParticlePartHolder
。
现在就可以个把各个不同的粒子部分组装起来了。如组装一个具有位置、速度、颜色和寿命等属性的粒子我们可以这样写代码:
TParticlePolicy
<
boost::mpl::vector<
SParticlePos,
SParticleVel,
SParticleColor,
SParticleLife
>
>
|
看到这里也许您还有一些不太清楚,让我们用一张继承结构图来说明:
现在我们已经可以很方便的组装我们所需要的粒子结构了,但任存在一个问题。每当我们需要加入一个新的粒子部分时必须自定义一个类似
SParticlePos
的结构,如果我们设计的是一个粒子的拆分部分非常多的系统这无疑是一件非常枯燥的差事。有没有什么方法可以解决呢?我们不妨定义一个泛型的粒子部分:
//
粒子部分
template
<
typename
_Type
> structTParticlePart {
_Type
m_Value;
};
|
我们通过
_Type
模板参数来指定粒子部分的类型,比如原先的
SParticlePos
我们在使用
D3D
的平台上可以写成
TParticlePart< D3DXVECTOR3 >
。这样的设计看似很好,其实隐藏着一个问题。如我们在
D3D
平台上定义一个如下的粒子:
typedef TParticlePolicy
<
boost::mpl::vector<
TParticlePart< D3DXVECTOR3 >, //
位置
TParticlePart< D3DXVECTOR3 >, //
速度
TParticlePart< D3DXCOLOR >, //
颜色
TParticlePart< FLOAT >, //
寿命
>
> tMyParticle;
tMyParticle
p;
|
此时我们要访问粒子的位置部分应该写成
p.m_Value
,但如果你这样写编译器就会抱怨产生了歧义。原因是不论你需要访问的是位置、速度、颜色还是寿命都应该写成
p.m_Value
,如何告诉编译器你真正想要访问的部分呢?这就需要引入一个用于区分不同部分的
TPartDiff
模板类,同时对
TParticlePartHolder
和
TParticlePolicy
做一些修改:
//
区分粒子的不同部分
template
<
class
_Part,
class
_Diff
>
class
TPartDiff : public_Part
{};
//
粒子组装器
template
<
class
_PartIter,
class
_PartsVector
> classTParticlePartHolder
: publicTPartDiff<
boost::mpl::deref< _PartIter >::type,
boost::mpl::int_< _PartIter::pos::value >
>
, publicTParticlePartHolder< _PartIter::next, _PartsVector >
{};
template
<
class
_PartsVector
> classTParticlePartHolder< boost::mpl::end< _PartsVector >::type, _PartsVector >
: publicboost::blank
{};
//
粒子
template
<
class
_PartsVector
> structTParticlePolicy
: publicTParticlePartHolder< boost::mpl::begin< _PartsVector >::type, _PartsVector >
{
typedef
_PartsVector
tPartsVector;
//
部分数
static
const
int
s_ciNumOfParts = boost::mpl::size< _PartsVector >::type::value;
//
获取一个粒子部分
template< size_tnIndex >
TPartDiff< boost::mpl::at_c< _PartsVector, nIndex >::type, boost::mpl::int_< nIndex > >& Part( void ) {
return *this;
}
//
获取一个粒子部分
template< size_tnIndex >
const
TPartDiff< boost::mpl::at_c< _PartsVector, nIndex >::type, boost::mpl::int_< nIndex > >& Part( void ) const {
return *this;
}
|
TPartDiff
接受两个模板参数第一个是形如
TParticlePart< D3DXVECTOR3 >
的粒子部分,第二个是用此粒子部分在粒子定义中的索引位置转换的类型(通过
boost::mpl::int_<>
可以将常量转换为类型)用于区分。
TParticlePartHolder
不再直接继承粒子部分,而是通过
TPartDiff
继承带有区分的粒子部分。
TParticlePolicy
引入了一个公共模板成员函数
Part()
用于访问不同的粒子部分,其有一个模板参数用于指定所需要访问的粒子部分的索引,这个函数还有一个
Const
版本。如果我们要访问以上定义的
tMyParticle
类型的粒子
p
的不同部分我们可以写成:
p.Part< 0 >().m_Value;
//
访问粒子的位置
p.Part< 1 >().m_Value;
//
访问粒子的速度
p.Part< 2 >().m_Value;
//
访问粒子的颜色
p.Part< 3 >().m_Value;
//
访问粒子的寿命
|
二、
可制定行为的粒子系统
有了粒子结构,接下来的工作就是制定我们的粒子系统了。任何一个粒子系统都可以被分成
3
个部分:初始化器——用于初始化每一个刚刚产生的新粒子;更新器——用于在每一桢更新粒子的状态;死亡触发器——用于定义粒子死亡时所引发的事件。由于粒子结构的不同,而不同的粒子部分又需要做不同的初始化和更新,所以对于整个粒子的初始化器和更新器需要做类似粒子部分的组装。
有了以上的粒子结构,初始化器,更新器和死亡触发器我们就可以开始组装完整的粒子系统了:
//
粒子系统
template
<
typename
_ParticleType, //
粒子结构
size_t
nLifeIndex, //
粒子寿命所在粒子结构中的索引
size_t
_Num, //
粒子总数
class
_InitializePolicy, //
粒子初始化器
class
_ActionPolicy, //
粒子更新器
class
_DeadPolicy
= TNilDeadTrigger< _ParticleType > //
死亡触发器
> classTParticleSystem
: boost::noncopyable
{
public
:
//
粒子类型
typedef
_ParticleType
tParticle;
//
初始化器类型
typedef
_InitializePolicy
tInitializer;
//
更新器类型
typedef
_ActionPolicy
tActor;
//
死亡触发器类型
typedef
_DeadPolicy
tDeadTrigger;
protected
:
//
粒子总数
static
const
int
s_ciNum = _Num;
//
粒子数组
boost::array< tParticle, s_ciNum > m_aParticles;
//
初始化器
tInitializer
m_Initializer;
//
更新器
tActor
m_Actor;
//
死亡触发器
tDeadTrigger
m_DeadTrigger;
//
当前活动粒子数
size_t
m_nCurrentCount;
public
:
//
构造函数
TParticleSystem( void ) : m_nCurrentCount( 0 ) { }
//
析构函数
virtual ~TParticleSystem( void ) { }
//
重置
inline
void
Reset( void ) { m_nCurrentCount = 0; }
//
获取初始化器
inline
tInitializer& Initializer( void ) { returnm_Initializer; }
inline
const
tInitializer& Initializer( void ) const { m_Initializer; }
//
获取更新器
inline
tActor& Actor( void ) { returnm_Actor; }
inline
const
tActor& Actor( void ) const { m_Actor; }
//
获取死亡触发器
inline
tDeadTrigger& DeadTrigger( void ) { returnm_DeadTrigger; }
inline
const
tDeadTrigger& DeadTrigger( void ) const { m_DeadTrigger; }
//
获取粒子数组的指针
inline
const
tParticle* GetParticles( void ) const {
if( ParticlesCount() == 0 ) {
return
NULL;
}
return
boost::addressof( m_aParticles[0] );
}
//
获取最大粒子数
inline
size_t
MaxParticles( void ) const {
return
s_ciNum;
}
//
获取当前活动粒子数
inline
size_t
ParticlesCount( void ) const {
return
m_nCurrentCount;
}
//
发射指定数目的粒子
void
Emit( size_tnAmount ) {
//
是否已经达到最大粒子数?
if( ( ParticlesCount() + nAmount ) > MaxParticles() ) {
nAmount = MaxParticles() - ParticlesCount();
}
if( nAmount > 0 ) {
//
发射粒子
size_t
nCnt = m_nCurrentCount;
m_nCurrentCount += nAmount;
for( ; nCnt < m_nCurrentCount; ++nCnt ) {
Init< 0 >( m_aParticles[nCnt], m_Initializer );
}
}
}
//
更新
void
Update( doubledElapsedTime ) {
for( size_tnCnt = 0; nCnt < m_nCurrentCount; ) {
//
更新每一个活动的粒子
Update< 0 >( dElapsedTime, m_aParticles[nCnt], m_Actor );
//
杀掉所有寿命为0或负数的粒子
if( m_aParticles[nCnt].Part< nLifeIndex >().m_Value <= 0.0 ) {
//
移除死亡的粒子,移动最后一个粒子到当前位置
m_DeadTrigger.On( m_aParticles[nCnt] );
m_aParticles[nCnt] = m_aParticles[m_nCurrentCount - 1];
//
当前活动粒子数减一
--m_nCurrentCount;
} else {
//
处理下一个粒子
++nCnt;
}
}
}
private
:
//
执行初始化动作
template< size_tnIndex >
void
Init( tParticle& p, tInitializer& i ) {
i.Part< nIndex >().Action< nIndex >( p );
Init< nIndex + 1 >( p, i );
}
template<>
void
Init< tParticle::s_ciNumOfParts >( tParticle&, tInitializer& ) {
}
//
执行更新动作
template< size_tnIndex >
void
Update( constdouble& dElapsedTime, tParticle& p, tActor& a ) {
a.Part< nIndex >().Action< nIndex >( dElapsedTime, p );
Update< nIndex + 1 >( dElapsedTime, p, a );
}
template<>
void
Update< tParticle::s_ciNumOfParts >( constdouble&, tParticle&, tActor& )
{
}
};
|
整个
TParticleSystem
模板类其实很简单,这里只对几个关键点进行说明。
TParticleSystem
一共接受
6
个模板参数,第一个
_ParticleType
为此粒子系统所需要处理的粒子结构;第二个
nLifeIndex
为粒子寿命部分所在粒子结构中的索引(每一个粒子都必须有一个寿命部分,
TParticleSystem
将使用此处索引所指的粒子部分来判断粒子是否死亡);第三个
_Num
是整个粒子系统所能容纳的最大粒子数;第四个
_InitializePolicy
是用于初始化粒子的初始化器;第五个
_ActionPolicy
是用于更新粒子的更新器;第六个
_DeadPolicy
是用于处理粒子死亡的死亡触发器。
TParticleSystem
有两个主要的成员函数
Emit
和
Update
。
Emit
用于发射指定数目的粒子其中需要说明的是用于初始化粒子的一条语句:
Init< 0 >( m_aParticles[nCnt], m_Initializer )
它调用的是一个带有一个数值型模板参数的成员函数
Init
,
Init
有一个模板参数
nIndex
,用于指定当前所初始化的粒子部分。
Init
内部有一个递归调用,每次递归把索引指加一使用不同的粒子部分初始化器初始化不同的粒子部分:
i.Part< nIndex >().Action< nIndex >( p );
如上的调用表示从第
0
部分开始初始化粒子,直至所有部分初始化完毕。
Update
与
Emit
的运作机制类似,通过语句:
Update< 0 >( dElapsedTime, m_aParticles[nCnt], m_Actor );
从第
0
部分开始更新整个粒子。更新完粒子后还通过语句:
if( m_aParticles[nCnt].Part< nLifeIndex >().m_Value <= 0.0 )
检测当前粒子是否死亡,这里就用到了先前设置的一个模板参数
nLifeIndex
。如果死亡则使用死亡触发器做相应的处理:
m_DeadTrigger.On( m_aParticles[nCnt] );
此外
Update
还接受一个参数
dElapsedTime
,用于表示上次更新到此次更新的间隔。
三、
初始化器
初始化器是整个粒子系统的重要组成部分,每个初始化器都必须有个一公共的带有一个
size_t
型模板参数的
Action
成员函数,
TParticleSystem
将调用此函数对粒子进行初始化,模板参数
nIndex
用于指定所需要初始化的粒子部分。系统中内建了几个常用的初始化器:
//
空初始化器
template
<
class
_ParticleType
> classTNilInitializer {
protected
:
typedef
_ParticleType
tParticle;
public
:
template< size_tnIndex >
void
Action( tParticle& ) {
}
};
|
TNilInitializer
是一个空初始化器,它有一个模板参数,
_ParticleType
用于指定所需要初始化的粒子类型,如果指定其为某个粒子部分的初始化器系统将不对此粒子部分进行初始化动作。
//
常量初始化器
template
<
class
_ParticleType,
typename
_Type
> classTConstantInitializer {
protected
:
typedef
_ParticleType
tParticle;
typedef
_Type
tType;
typedef
boost::mpl::if_c<
boost::is_pod< tType >::value,
tType,
boost::add_reference< boost::add_const< tType >::type >::type
>::typetParaType;
//
初始化的值
tType
m_Value;
public
:
//
设定初始化值
void
Set( tParaTypeValue ) { m_Value = Value; }
//
执行初始化
template< size_tnIndex >
void
Action( tParticle& p ) {
p.Part< nIndex >().m_Value = m_Value;
}
};
|
TConstantInitializer
是一个常量初始化器,它有两个模板参数,
_ParticleType
用于指定所需要初始化的粒子类型,
_Type
用于指点其将要初始化的粒子部分的类型。公共成员函数
Set
用于设置初始化的值。
//
矩阵变换初始化器
template
<
class
_ParticleType,
typename
_MatrixType
> classTTransformInitializer {
protected
:
typedef
_ParticleType
tParticle;
typedef
_MatrixType
tMatrix;
//
矩阵
tMatrix
m_Matrix;
public
:
//
构造函数
TTransformInitializer( void )
: m_Matrix( 1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0 )
{}
//
设置变换矩阵
void
Set( consttMatrix& Matrix ) {
m_Matrix = Matrix;
}
//
执行初始化
template< size_tnIndex >
void
Action( tParticle& p ) {
p.Part< nIndex >().m_Value *= m_Matrix;
}
};
|
TTransformInitializer
是一个矩阵变换初始化器,它有两个模板参数,
_ParticleType
用于指定所需要初始化的粒子类型,
_MatrixType
用于指定变换矩阵的类型。公共成员函数
Set
用于设置变换矩阵。使用此初始化器还必须为
p.Part< nIndex >().m_Value
的类型定一个接受
tMatrix
类型的
*=
操作符,用于执行变换操作。
//
一维矢量随机初始化器
template
<
class
_ParticleType,
class
_RandGenerator,
class
_RandDistribution
> classTUniformRandomVec1Initializer {
protected
:
typedef
_ParticleType
tParticle;
typedef
_RandGenerator
tGenerator;
typedef
_RandDistribution
tDistribution;
typedef
boost::variate_generator< _RandGenerator, _RandDistribution > tVarGen;
//
数值生成器
tVarGen
m_VGen;
public
:
TUniformRandomVec1Initializer( void )
: m_VGen( tGenerator( 1 ), tDistribution() )
{}
//
设置种子
void
Seed( boost::uint32_tnSeed ) {
m_VGen.engine().seed( nSeed );
}
//
设置分布器
void
SetDist( consttDistribution& Dist ) {
m_VGen.distribution() = Dist;
}
//
执行初始化
template< size_tnIndex >
void
Action( tParticle& p ) {
p.Part< nIndex >().m_Value = m_VGen();
}
};
|
TUniformRandomVec1Initializer
是一个一维随机初始化器,它接受
3
个模板参数,
_ParticleType
用于指定所需要初始化的粒子类型,
_RandGenerator
用于指定随机数生成器(详细说明请参看
Boost.Random
的文档),
_RandDistribution
用于指定随机数分布器(详细说明请参看
Boost.Random
的文档)。公共成员函数
Seed
用于设置随机数生成器的种子,
SetDist
用于设置随机数分布器。
TUniformRandomVec2Initializer
二维随机初始化器,
TUniformRandomVec3Initializer
三维随机初始化器,
TUniformRandomVec4Initializer
四维随机初始化器,
TSphereRandomVec2XYInitializer
三维
XY
平面圆形随机初始化器,
TSphereRandomVec2XZInitializer
三维
XZ
平面圆形随机初始化器,
TSphereRandomVec2YZInitializer
三维
YZ
平面圆形随机初始化器,
的使用都与此类似,详细使用请参看本文所附例程,详细实现请参看本文所附源代码及
Boost.Random
文档。
如果以上提到的内建初始化器中并没有您需要的,那么动手自定义一个初始化器也是一件非常轻松愉快的事情。
//
根据寿命修正退色因子
template
<
class
_ParticleType,
size_t
nFadeIndex
> classTFixFadeByLifeInitializer {
protected
:
typedef
_ParticleType
tParticle;
public
:
//
执行初始化
template< size_tnIndex >
void
Action( tParticle& p ) {
p.Part< nFadeIndex >().m_Value = 1.0 / p.Part< nIndex >().m_Value;
}
};
|
这里我们自定义了一个根据初始寿命值修正退色因子的初始化器,它接受两个模板参数,
_ParticleType
用于指定所需要初始化的粒子类型,
nFadeIndex
用于指定退色因子在粒子结构中的索引。
四、更新器
更新器和初始化器一样也粒子系统重要的组成部分,每个更新器都必须有个一公共的带有一个
size_t
型模板参数的
Action
成员函数,此函数接受一个从上次更新到现在的时间间隔作为参数,
TParticleSystem
将调用此函数对粒子进行更新,模板参数
nIndex
用于指定所需要更新的粒子部分。系统中内建了几个常用的更新器:
//
空更新器
template
<
class
_ParticleType
> classTNilActor {
protected
:
typedef
_ParticleType
tParticle;
public
:
template< size_tnIndex >
void
Action( constdouble&, tParticle& ) {
}
};
|
TNilActor
是一个空更新器
,
它有一个模板参数,
_ParticleType
用于指定所需要更新的粒子类型,如果指定其为某个粒子部分的更新器系统将不对此粒子部分进行更新动作。
//
移动位置更新器
template
<
class
_ParticleType,
size_t
nVelIndex
> classTMovePosActor {
protected
:
typedef
_ParticleType
tParticle;
public
:
//
执行更新
template< size_tnIndex >
void
Action( constdouble& dTime, tParticle& p ) {
p.Part< nIndex >().m_Value += p.Part< nVelIndex >().m_Value * dTime;
}
};
|
TMovePosActor
是一个移动位置更新器,它根据当前的速度值更新位置,只能应用于粒子的位置部分。它有两个模板参数,
_ParticleType
用于指定所需要更新的粒子类型,
nVelIndex
用于指定粒子的速度部分在粒子结构中的索引。
//
重力速度更新器
template
<
class
_ParticleType,
typename
_Vector3Type
> classTGravityVelActor {
protected
:
typedef
_ParticleType
tParticle;
typedef
_Vector3Type
tVector3;
//
重力加速度
tVector3m_vGravity;
public
:
TGravityVelActor( void )
: m_vGravity( tVector3( 0.0,-9.8, 0.0 ) )
{}
//
设置重力加速度
void
SetGravity( consttVector3& vec3 ) {
m_vGravity = vec3;
}
//
执行更新
template< size_tnIndex >
void
Action( constdouble& dTime, tParticle& p ) {
p.Part< nIndex >().m_Value += m_vGravity * dTime;
}
};
|
TGravityVelActor
是一个重力速度更新器,它根据指定的重力加速度更新速度,只能应用于粒子的速度部分。它有两个模板参数,
_ParticleType
用于指定所需要更新的粒子类型,
_Vector3Type
用于指定重力加速度的类型。公共成员函数
SetGravity
终于设置重力加速度。
//
寿命逝去更新器
template
<
class
_ParticleType
> classTElapseLifeActor {
protected
:
typedef
_ParticleType
tParticle;
public
:
//
执行更新
template< size_tnIndex >
void
Action( constdouble& dTime, tParticle& p ) {
p.Part< nIndex >().m_Value -= dTime;
}
};
|
TElapseLifeActor
是一个寿命逝去更新器,它根据逝去的时间减少寿命,只能应用于粒子的寿命部分。它有一个模板参数,
_ParticleType
用于指定所需要更新的粒子类型。
//
死亡断言更新器
template
<
class
_ParticleType
> classTDeadLifeActor {
protected
:
typedef
_ParticleType
tParticle;
typedef
boost::function< bool ( consttParticle& ) > tfnDead;
//
死亡断言函数对象
tfnDead
m_DeadPred;
public
:
// Set dead predicate
void
SetDeadPredicate( consttfnDead& DeadPred ) { m_DeadPred = DeadPred; }
//
执行更新
template< size_tnIndex >
void
Action( constdouble&, tParticle& p ) {
if( ! m_DeadPred.empty() ) {
if( m_DeadPred( p ) ) p.Part< nIndex >().m_Value = -0.0f;
}
}
};
|
TDeadLifeActor
是一个死亡断言更新器,它调用死亡断言函数对象来确认粒子是否死亡,如果死亡则修改寿命值为
-0.0
,只能应用于粒子的寿命部分。它有一个模板参数,
_ParticleType
用于指定所需要更新的粒子类型。
如果以上提到的内建更新器中并没有您需要的,那么动手自定义一个更新器也是一件非常轻松愉快的事情。
//
退色更新器
template
<
class
_ParticleType,
typename
_ColorType,
size_t
nFadeIndex
> classTFadeColorActor {
protected
:
typedef
_ParticleType
tParticle;
typedef
_ColorType
tColor;
//
颜色一
tColor
m_Value1;
//
颜色二
tColor
m_Value2;
public
:
TFadeColorActor( void )
: m_Value1( tColor( 1.0, 1.0, 1.0, 1.0 ) )
, m_Value2( tColor( 0.0, 0.0, 0.0, 1.0 ) )
{}
//
设定颜色一
void
SetColorOne( consttColor& col ) {
m_Value1 = col;
}
//
设定颜色二
void
SetColorTwo( consttColor& col ) {
m_Value2 = col;
}
//
执行更新
template< size_tnIndex >
void
Action( constdouble& dTime, tParticle& p ) {
p.Part< nIndex >().m_Value += ( m_Value2 - m_Value1 ) * p.Part< nFadeIndex >().m_Value * dTime;
}
};
|
这里我们自定义了一个根据寿命值使粒子颜色由颜色一退为颜色二的更新器,只能应用于粒子的颜色部分。它有三个模板参数,
_ParticleType
用于指定所需要更新的粒子类型,
_ColorType
用于指定颜色类型,
nFadeIndex
用于指定退色因子在粒子结构中的索引。
五、死亡触发器
相对于初始化器和更新器,死亡触发器要简单一些,在有粒子死亡时
On
成员函数将被调用。系统内建了两个死亡触发器。
//
空死亡触发器
template
<
class
_ParticleType
> classTNilDeadTrigger {
protected
:
typedef
_ParticleType
tParticle;
public
:
void
On( consttParticle& ) const {
}
};
|
TNilDeadTrigger
是一个空死亡触发器,它不对粒子的死亡做任何的处理。它有一个模板参数,
_ParticleType
用于指定所需要更新的粒子类型。
//
简单死亡触发器
template
<
class
_ParticleType
> classTSimpleDeadTrigger {
protected
:
typedef
_ParticleType
tParticle;
typedef
boost::function< void ( consttParticle& ) > tfnDeadTrigger;
//
死亡触发函数对象
tfnDeadTrigger
m_DeadTrigger;
public
:
//
设置死亡触发函数对象
void
SetDeadTrigger( consttfnDeadTrigger& DeadTrigger ) {
m_DeadTrigger = DeadTrigger;
}
//
处理死亡事件
void
On( consttParticle& p ) const {
if( ! m_DeadTrigger.empty() ) m_DeadTrigger( p );
}
};
|
TSimpleDeadTrigger
是一个简单死亡触发器,当有粒子死亡时他调用死亡触发函数对象,公共成员函数
SetDeadTrigger
用于设置死亡触发函数对象。它有一个模板参数,
_ParticleType
用于指定所需要更新的粒子类型。
六、其他
完成了以上工作后我几乎几经设计出了易于扩展,适应性强的泛型粒子系统了。为什么我要说几乎呢?不知道您有没有发现现在一个粒子部分只能对应一个初始化器,和一个更新器。如果我们需要用两个初始化器来初始化一个粒子部分,用两个更新器来更新一个粒子部分该怎么办呢?这就需要设计一个结合两个初始化器和结合两个更新器的工具。
//
整合两个初始化器
template
<
class
_ParticleType,
class
_PolicyOne,
class
_PolicyTwo
> classTBothInitializerPolicy
: public_PolicyOne
, public_PolicyTwo
{
protected
:
typedef
_ParticleType
tParticle;
public
:
//
执行初始化
template< size_tnIndex >
void
Action( tParticle& p ) {
_PolicyOne::Action< nIndex >( p );
_PolicyTwo::Action< nIndex >( p );
}
};
|
TBothInitializerPolicy
整合了两个初始化器,使他们共同作用于一个粒子部分。它有三个模板参数,
_ParticleType
用于指定所需要初始化的粒子类型,
_PolicyOne
用于指定第一个初始化器,
_PolicyTwo
用于指定第二个初始化器。
//
整合两个更新器
template
<
class
_ParticleType,
class
_PolicyOne,
class
_PolicyTwo
> classTBothActorPolicy
: public_PolicyOne
, public_PolicyTwo
{
protected
:
typedef
_ParticleType
tParticle;
public
:
//
执行更新
template< size_tnIndex >
void
Action( constdouble& dTime, tParticle& p ) {
_PolicyOne::Action< nIndex >( dTime, p );
_PolicyTwo::Action< nIndex >( dTime, p );
}
};
|
TBothActorPolicy
整合了两个更新器,使他们共同作用于一个粒子部分。它有三个模板参数,
_ParticleType
用于指定所需要更新的粒子类型,
_PolicyOne
用于指定第一个更新器,
_PolicyTwo
用于指定第二个更新器。
七、附件
例子程序:
TPS.rar
http://uj86c1.chinaw3.com/tmp/TPS.rar
(源码已无法下载,谁能提供个源码?)
源程序:
looParticleSystem.hpp
http://uj86c1.chinaw3.com/tmp/looParticleSystem.hpp
编译需求:
Intel C++ Compiler v8.0
,
vs2003.net
,
boost 1.31.0
八、参考资料
Designing an Extensible Particle System using C++ and Templates,
By Kent "_dot_" Lai