三维粒子系统在AS2中的实现

三维粒子系统在AS2中的实现   

Emil Matthew(EmilMatthew@126.com)

摘要: 

       本文通过粒子系统及三维粒子透视投影变换,运用Flash AS2及其开发环境,实现了基本的三维粒子系统。并结合相关的曲线及运动方式,实现了抛体运动,Fermat 螺线上升运动以及龙卷风效果。表现了一定的数字艺术层的美感。

 

关键词: 粒子系统,三维,数字艺术

 

The implement of 3D Particle System in AS2

                      Emil Matthew(EmilMatthew@126.com)

Abstract: 

       In this article, with as2 in the flash dev platform, by implementing the project transformation with 3d particle system , I realize a basic particle 3d particle system . Also , with the implementing of some curve and move method , I realize the parabola motion effect , Fermat spiral upward moving effect and the hurricane simulation effect . These effects express some extend of aesthetic feeling in the level of digital art.

 

Key Words: Particle System, 3D, Digital Art

      

1前言:

       三维粒子系统是一类令人感到激动又十分有趣的动画程序。它的实现方式主要需要用基于粒子系统构建的图形学,动力学以及数字艺术等多方面的知识。[1]介绍了基本的三维视图通过透视投影变换到二维场景的方式。[2]介绍了一个基本的粒子系统的实现。在这两篇文章的基础上,再结合比较简单的运动学方面的知识,在本文中实现了基本的三维粒子系统,并实现了抛体运动,Fermat 螺线上升运动以及龙卷风效果。这些作品表现了一定的数字艺术层的美感。

       另外,开发选用的是基于AS2语言的Flash开发平台,这种开发模式具有以下优点:

1)      Flash 播放器具有极高的普及率,而且swf文件格式是跨平台的。

2)      Flash 的失量图形处理模式极强,适合网络传播图像,动画及各类互动效果。

3)      Flash 平台下的交互动画开发方便,采用了AS2语言可以面向对象的模式来组织程序结构。交互效果可以实现的非常到位。可以说,是一种面像图形开发的脚本语言。(正如Matlab可以说是面向科学工程计算的脚本语言一样。)

 

当然,这种模式也是有缺点的,比如Flash播放器在处理一些较大运算时的效果不是很

理想。

由于这个程序重点在实现相应的图形效果上,主要是测试之用,所以采用Flash AS2

来进行是非常合适的。

 

2程序总体思路及关键部分:

2.1粒子系统的框架:

[2]中已有叙述,这里简要回顾之:

       Initialization

 

 

while(runFlag)

       {

                     For all particles

                     {

                            If(current particle is not lived)

                            {

                                          Init this particle.

}

                            Else if(current particle is out of the showing area)

                            {

                                   Current particle set to dead.

}

}

}

 

2.2三维粒子在二维场景的透视成像:

       [1]中已有详述,这里简要回顾之:

       对于一个在三维场景中的点p(x ,y ,z)

       其在距离原点距离为d处的位于z轴正半轴上的点(0,0,d)而言,其对应到二维投影面的点p’ (x’,y’)有如下计算公式:

              x’=x/(-z/d+1)               

y’=y/(-z/d+1)        

                                                                

                                                               1                            

 

       2.3.1抛体运动模式关键:

       抛体运动在这里的右手坐标系(图1)中,是指以y为竖直运方向作上抛运动,x , z方向以固定速度运动的方式。我们在公园里看到的许多喷泉就是这样的例子。

       由于初始化后所有粒子统一生成,所以要看到类似喷泉的效果,需要待几个粒子生成周期之后,生成粒子与死亡粒子可以持续交替时,便会呈现出不错的效果。图2展示了300粒子在程序运行过几个粒子生成周期后的效果。

       相应初始程序、参数及运动函数如下:

       //data set

tmp=Math.random();                                        _root.particleArr[i].initV_A((2+random(3))*Math.cos(2*Math.PI*tmp),15+random(10),(2+random(3))*Math.sin(2*Math.PI*tmp),0,-0.5+0.15*Math.random(),0);

         _root.particleArr[i].initPos(gX0-10+random(20),gY0-50+random(150),gZ0-10+random(20));

                    

       //motion effect mode

       for(var i:Number=0;i<_root.gParticleNum;i++)

                     {

                                   if(_root.particleArr[i].isLived())

                                   {

                                          _root.particleArr[i].moveCal();

                                          _root.particleArr[i].moveShow();

                                   }

                     }

2

      

       2.3.2Fermat 螺线上升运动:

       Fermat螺线的极坐标表达为:

                            ρ=a*θ ^(1/2)

       这里要做的,就是将θ角在某粒子与xz平面垂直的截平面上对应的Fermat螺线极坐标转换成xz坐标值。再进行相应的更新即可。与前一种不同的是,这里只有y轴方向需要用到vy,而x,z轴的位置则由于采用了坐标控制模式,所以不需要使用vx,vz。为了整体程序设计上的统一,可将vx,vz设定为0

       与上一个抛物线型运动不同,这里的Fermat螺线运动如果新生粒子与死亡粒子构成了持续生成关系时,将使画面较为混乱。因此,需要将粒子的初始y值及vy,ay设定的靠近些。

       可以看到,团状的粒子簇旋转上升后又扩散下降,无序之中体现着一丝韵律,我将这样的效果命名为“银河落九天”,不知诸位为以为如何?(参图3)

       //data set

tmp=Math.random();

       _root.particleArr[i].initV_A(0,18+random(2),0,0,-0.5+0.1*Math.random(),0);

                                  

       _root.particleArr[i].initRotateEle(0,0,2*Math.PI*i/_root.gParticleNum);

              _root.particleArr[i].initPos(gX0-15+random(30),gY0-5+random(10),gZ0-15+random(30));

                    

       _root.particleArr[i].fermatSpiralA=15+random(10);

 

       //motion effect mode

       for(var i:Number=0;i<_root.gParticleNum;i++)

                     {

                                   if(_root.particleArr[i].isLived())

                                   {

                                         

                                          _root.particleArr[i].accTheta(Math.PI/18);

                                         

                                          _root.particleArr[i].moveCalFermatSpiralWithY();

                                          _root.particleArr[i].moveShow();

                                   }

                     }

3

 

       2.3.3龙卷风效果:

       龙卷风效果,这里没有采用从坐标原点生成的模式,而是直接摸拟了生成后运动的效果,难度降低了不小。

       可以这样近似看待龙卷风的模拟,每个粒子都以y轴为中心,以不同的运动半径作圆周运动。各粒子的运动半径从下往上递增。

       以上面的设计理念,便可生成一种倒锥型的龙卷风,如图4。显然,在这种模式中,没有粒子会死亡。

//data set

_root.particleArr[i].initV_A((0.5*Math.random())*Math.cos(2*Math.PI*tmp),0,(0.5*Math.random())*Math.sin(2*Math.PI*tmp),0,0,0);

                                                                      _root.particleArr[i].initRotateEle(0,0,2*Math.PI*Math.random());

                                                

_root.particleArr[i].initPos(gX0+(i+1)*2-2+random(4),gY0+50-baconHeight/_root.gParticleNum*i-random(50),gZ0+(i+1)*2-2+random(4));

                    

baconHeight=400

//motion effect mode

for(var i:Number=0;i<_root.gParticleNum;i++)

                     {

                                   if(_root.particleArr[i].isLived())

                                   {

                                          _root.particleArr[i].accTheta(Math.PI/18);

                                         

                                          _root.particleArr[i].moveWithY();

                                                       

                                          _root.particleArr[i].moveShow();

                                   }

                     }

4

 

       如果再对粒子生成加以改进,即位于龙卷风高度的75%以下的部分主要生成一个类似柱状的效果,而对于龙卷风高度的75%以上部分则扩散出去,形成一个更为逼真的效果。(如图5

       1控制生成高度及速度:

if(i<0.7*_root.gParticleNum)

       {

       _root.particleArr[i].initV_A((5*Math.random())*Math.cos(2*Math.PI*tmp),0,(5*Math.random())*Math.sin(2*Math.PI*tmp),0,0,0);                   

                            _root.particleArr[i].initPos(gX0-50+random(100),gY0+50-baconHeight/_root.gParticleNum*i-random(50),gZ0-50+random(100));

       }

       else

       {            _root.particleArr[i].initV_A((5*Math.random())*Math.cos(2*Math.PI*tmp),0,(5*Math.random())*Math.sin(2*Math.PI*tmp),0,0,0);                   

       if(random(100)<50)                            _root.particleArr[i].initPos(gX0+100+random(100),gY0+25-baconHeight/_root.gParticleNum*i-random(25),gZ0+100+random(100));

       else                       _root.particleArr[i].initPos(gX0-100-random(100),gY0+25-baconHeight/_root.gParticleNum*i-random(25),gZ0-100-random(100));

       }

 

2保持上下不同的旋转速度:

       if(i<0.7*_root.particleNum)

              _root.particleArr[i].accTheta(Math.PI/24+0.05*Math.random());

       else

              _root.particleArr[i].accTheta(Math.PI/18+0.1*Math.random());

      

51000粒)

 

3关键代码:

3.1粒子类

import E3DPack.*;

import SColDet.*;

import SMotion.*;

class E3DPack.E3DPhyNode extends E3DNode

{

              //--location properties extends.--

              /*

              public var x:Number;

              public var y:Number;

              public var z:Number;

              */

             

              public var bufferX:Number=0;//for the clear,re-draw mode here.

              public var bufferY:Number=0;

              public var bufferZ:Number=0;

             

              //--dynamic properties--

             

              public var m:Number=0;//mass

      

              public var g:Number=0;//gravity

      

              public var pF:Number=0;//Positive forces,attention here UpCase!!!!!!!

              //Because the compiler was not so perfect as you think ,add a p here to prepare for the case.

              public var r:Number =0;//when it become a ball---radius.

      

              public var v :Number=0;//1 demision Velocity or together Velocity of vx ,vy

              public var vx:Number=0;

              public var vy:Number=0;

              public var vz:Number=0;

      

              public var f :Number=0;//fraction forces.

              public var fx:Number=0;

              public var fy:Number=0;

              public var fz:Number=0;

      

              public var a :Number=0;//acclerate v

              public var ax:Number=0;

              public var ay:Number=0;

              public var az:Number=0;

             

              public var fN:Number=0;

              public var aN:Number=0;

              public var theta:Number=0;         

              public var aNXZ:Number=0;         //the accleration value for rotate with y-axis

              public var rXZ:Number=0;           //the radius in rotate with y axis.

             

             

              //for fermat spirals only

              var fermatSpiralA:Number=5;

             

              //--life properties of particles--

              private static var DEAD:Number=0;

              private static var LIVED:Number=1;

              private var life:Number;

      

              private static var thisP:Object;

             

              //--ulti operation component--

              private var mMotionCom:RCSMove;

              private var mColDetCom:RCSColDet;

             

              //--function which is useful by extends.--

              /*

              public function E3DNode(inX:Number,inY:Number,inZ:Number)

              public function resetXYZ(inX:Number,inY:Number,inZ:Number)

              public function getPerspective(viewDistance:Number):Number

              public function transTo2DNode(viewDistance:Number):E2DNode

              public function transTo2DNode2(projectPos:Number):E2DNode

              public function rotateAroundZ(fi:Number):Void

              public function rotateAroundX(fi:Number):Void

              public function rotateAroundY(fi:Number):Void                                                                                                                                                       */

             

             

              //--self function--

              public function E3DPhyNode(inX:Number,inY:Number,inZ:Number)

              {

                            x=inX;

                            y=inY;

                            z=inZ;

              }

             

              public function setLife(lifeValue:Number):Void

              {

                     life=lifeValue;

              }

      

              public function getLife():Number

              {

                     return life;

              }

      

              public function isLived():Boolean

              {

                     return life==LIVED;

              }

             

              //-initialization functions-

              public function initThisPtr():Void

              {

                            thisP=this;

              }           

             

              public function initCom():Void

              {

                     mMotionCom=new RCSMove();

                     mColDetCom=new RCSColDet();

              }

             

 

public function initV_A(inVx:Number,inVy:Number,inVz:Number,inAx:Number,inAy:Number,inAz:Number):Void

              {    

                     this.vx=inVx;

                     this.vy=inVy;

                     this.vz=inVz;

                    

                     this.ax=inAx;

                     this.ay=inAy;

                     this.az=inAz;

              }

      

              public function initPos(inX:Number,inY:Number,inZ:Number):Void

              {

                     this.x=inX;

                     this.y=inY;

                     this.z=inZ;

                    

                     this.bufferX=this.x;

                     this.bufferY=this.y;

                     this.bufferZ=this.z;

              }

             

              public function initRotateEle(inFn:Number,inAn:Number,inTheta:Number):Void

              {

                     this.fN=inFn;

                     this.aN=inAn;

                     this.theta=inTheta;

              }

             

              //--move and show functions--

              public function moveCal():Void

              {

                     this.vx+=this.ax;

                     this.vy+=this.ay;

                     this.vz+=this.az;

                    

                     this.bufferX+=this.vx;

                     this.bufferY-=this.vy;

                     this.bufferZ+=this.vz;

              }

             

              public function moveShow():Void

              {

                     var tmp2DNode=new E2DNode(0,0);

             

                     /*global varialbe depends:

                     gNodeColor,gNodeTransparent,gOffsetX,gOffsetY

                                                                                                         */

                                                                                                        

                     //1.clear the old node

                     //in a uplever , global clear mode.

             

                     //2.update the position    ,actually ,there is no need to use bufferx,y,z here in flash.

                     //but the design here seem to be more flexible

                     this.x=this.bufferX;

                     this.y=this.bufferY;

                     this.z=this.bufferZ;

                    

                     //3.redraw.     

                     tmp2DNode=this.transTo2DNode2(_root.gZProjectDis);

                    

                     _root.gBrush.moveTo(_root.gOffsetX+Number(tmp2DNode.x)-1,_root.gOffsetY+Number(tmp2DNode.y)+1);

                           

                     _root.gBrush.lineTo(_root.gOffsetX+Number(tmp2DNode.x)+1,_root.gOffsetY+Number(tmp2DNode.y)-1);             

             

              }

             

              //--check functions--

             

              public function outDetect():Boolean

              {

             

                     if(this.y>400)

                            return true;

                     else

                            return false;

              }

             

              //--other ulti moving functions--

              public function accTheta(detaAng:Number):Void

              {

                     this.theta+=detaAng;

              }

             

              public function calANXZ():Void

              {

                     this.aNXZ=(this.vx*this.vx+this.vz*this.vz)/Math.sqrt(this.x*this.x+this.z*this.z);  

              }

                    

              public function calRXZ():Void

              {

                     this.rXZ=Math.sqrt(this.x*this.x+this.z*this.z);

              }

                    

              public function calAxAzInRotate():Void

              {

                     this.aN=(this.vx*this.vx+this.vz*this.vz)/Math.sqrt(this.x*this.x+this.z*this.z);

                    

                     this.ax=this.aN*Math.cos(this.theta);

                     this.az=this.aN*Math.sin(this.theta);

              }

             

              public function calAxAzInRotate2():Void

              {

                     this.ax=this.aNXZ*Math.cos(this.theta);

                     this.az=this.aNXZ*Math.sin(this.theta);

              }

             

              public function moveWithY():Void

              {

                     this.bufferX=this.rXZ*Math.cos(this.theta);

                     this.bufferZ=this.rXZ*Math.sin(this.theta);

              }

             

              public function moveCalFermatSpiralWithY():Void

              {

                     this.vy+=this.ay;

             

                     this.bufferX=this.fermatSpiralA*Math.sqrt(this.theta)*Math.cos(this.theta);

                    

                     this.bufferY-=this.vy;

                    

                     this.bufferZ=this.fermatSpiralA*Math.sqrt(this.theta)*Math.sin(this.theta);

              }

}

        

         3.2初始化函数(以抛物线运动作为示例)

function initParticles():Void

{

       for(var i:Number=0;i<_root.gParticleNum;i++)

       {

                     var tmp:Number=0;

                     var tmpVr:Number=0;

               _root.particleArr[i].setLife(LIVED);

               _root.particleArr[i].initThisPtr();

               _root.particleArr[i].initCom();

             

                      tmp=Math.random();

                    

                     //_root.initRotateEle(2,2,0);

                     _root.particleArr[i].initV_A((2+random(3))*Math.cos(2*Math.PI*tmp),15+random(10),(2+random(3))*Math.sin(2*Math.PI*tmp),0,-0.5+0.15*Math.random(),0);

                     _root.particleArr[i].initPos(gX0-10+random(20),gY0-50+random(150),gZ0-10+random(20));

                    

       }

      

}

 

       3.3主循环函数(以抛物线运动为基础)

function mainLoop():Void

{

       var tmp:Number=0;

      

       if(_root.gRunFlag)//a infinite looping system.

       {

                    

                     //--live check or regenerate particle--

                     for(var i:Number=0;i<_root.gParticleNum;i++)

                     {

                       if(!_root.particleArr[i].isLived())             

                                                 {

                                                

                                                         _root.particleArr[i].setLife(LIVED);

                                                       

                                                          tmp=Math.random();

                                                              _root.particleArr[i].initV_A((2+random(3))*Math.cos(2*Math.PI*tmp),15+random(10),(2+random(3))*Math.sin(2*Math.PI*tmp),0,-0.5+0.15*Math.random(),0);

                                                              _root.particleArr[i].initPos(gX0-10+random(20),gY0+random(50),gZ0-10+random(20));

                                                 }

                            else if(_root.particleArr[i].outDetect())

                            {

                                                 _root.particleArr[i].setLife(DEAD);

                            }

                     }

                    

                     //--particles updating and redraw.--

                    

                     //--last pic clear.     --

                     _root.gBrush.clear();

                    

                     //--new brush set   --

                     _root.gBrush.lineStyle(1,_root.gNodeColor,_root.gNodeTransparent);

                    

                     for(var i:Number=0;i<_root.gParticleNum;i++)

                     {

                                   if(_root.particleArr[i].isLived())

                                   {

                                         

                                          _root.particleArr[i].moveCal();

                                          _root.particleArr[i].moveShow();

                                   }

                     }

       }

}

      

4实验结论:

       本文论述了3D粒子系统的基本框架并实现了相应的运动效果,如抛体,龙卷风运动等,具有一定的参考意义。并且,对于基于更高级物理效果的运动模拟,如絮流等,作好了准备工作。

 

参考文献:

[1]Emil Matthew 叩开3D编程这扇门,http://blog.csdn.net/EmilMatthew/archive/2006/01/22/586472.aspx

 

[2]Emil Matthew,飞机躲避小游戏---是男人就撑100秒的制作,

http://blog.csdn.net/emilmatthew/archive/2006/03/25/638541.aspx

 

[3]螺线介绍, http://mathworld.wolfram.com/ArchimedeanSpiral.html

 

                                                                                                         程序完成日: 06/05/02

                                                                                                         文章完成日: 06/05/03

 

附录:

1测试程序下载:

http://emilmatthew.51.net/EmilPapers/06_173DParticle/code1.rar

http://emilmatthew.51.net/EmilPapers/06_173DParticle/code2.rar

http://emilmatthew.51.net/EmilPapers/06_173DParticle/code3.rar

http://emilmatthew.51.net/EmilPapers/06_173DParticle/code4.rar

 

2DOC文档下载:

http://emilmatthew.51.net/EmilPapers/06_173DParticle/doc.rar

 

若直接点击无法下载(或浏览),请将下载(或浏览)的超链接粘接至浏览器地址栏后按回车.若不出意外,此时应能下载.

若下载中出现了问题,请参考:

http://blog.csdn.net/emilmatthew/archive/2006/04/08/655612.aspx

在Python,`scipy`库主要用于科学计算,包括统计、优化、插值、积分等。要通过`scipy`展现三维Maxwell分布,通常我们会使用其概率密度函数(PDF)。Maxwell分布常用于描述粒子的速度分布,特别是在热力学系统。 首先,需要安装`scipy`库,如果尚未安装可以使用pip安装: ```bash pip install scipy ``` 然后,我们可以导入必要的模块并创建一个简单的三维Maxwell分布。这里我们将使用`numpy`库来生成数据,因为`scipy.stats`直接依赖于`numpy`: ```python import numpy as np from scipy.stats import multivariate_normal as mvn # 定义Maxwell分布的均值(平均速度)和标准差(温度) mu = [0, 0, 0] # 平均速度 (x, y, z) sigma = 1 # 温度 (假设单位是m/s^2) # 创建一个二维数组,表示空间坐标 (x, y, z) 的网格 x, y, z = np.meshgrid(np.linspace(-5, 5, 100), np.linspace(-5, 5, 100), np.linspace(-5, 5, 100)) # 创建三维数组,将所有点组合在一起 points = np.column_stack((x.flat, y.flat, z.flat)).reshape(x.shape + (-1,)) pdf = mvn.pdf(points, mean=mu, cov=sigma**2 * np.eye(3)) # 系数σ^2是因为速度方差与温度成正比 # 可视化三维分布 import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D fig = plt.figure() ax = fig.add_subplot(111, projection='3d') ax.plot_surface(x, y, pdf.reshape(x.shape), cmap='viridis', linewidth=0.2, antialiased=True) ax.set_xlabel('X') ax.set_ylabel('Y') ax.set_zlabel('Probability Density') plt.show() # 显示Maxwell分布图
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值