三维粒子系统在AS2中的实现 [收藏本页]
  • 作者:EmilMatthew 来源:闪吧 发布时间:2006-5-14 12:24:33
  • 摘要:
    本文通过粒子系统及三维粒子透视投影变换,运用Flash AS2及其开发环境,实现了基本的三维粒子系统。并结合相关的曲线及运动方式,实现了抛体运动,Fermat 螺线上升运动以及龙卷风效果。表现了一定的数字艺术层的美感。

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

    1前言:
    三维粒子系统是一类令人感到激动又十分有趣的动画程序。它的实现方式主要需要用基于粒子系统构建的图形学,动力学以及数字艺术等多方面的知识。[1]介绍了基本的三维视图通过透视投影变换到二维场景的方式。[2]介绍了一个基本的粒子系统的实现。在这两篇文章的基础上,再结合比较简单的运动学方面的知识,在本文中实现了基本的三维粒子系统,并实现了抛体运动,Fermat 螺线上升运动以及龙卷风效果。这些作品表现了一定的数字艺术层的美感。
    另外,开发选用的是基于AS2语言的Flash开发平台,这种开发模式具有以下优点: 1) Flash 播放器具有极高的普及率,而且swf文件格式是跨平台的。
    2) Flash 的失量图形处理模式极强,适合网络传播图像,动画及各类互动效果。
    3) Flash 平台下的交互动画开发方便,采用了AS2语言可以面向对象的模式来组织程序结构。交互效果可以实现的非常到位。可以说,是一种面像图形开发的脚本语言。(正如Matlab可以说是面向科学工程计算的脚本语言一样。)

    当然,这种模式也是有缺点的,比如Flash播放器在处理一些较大运算时的效果不是很
    理想。
    由于这个程序重点在实现相应的图形效果上,主要是测试之用,所以采用Flash 的 AS2
    来进行是非常合适的。

    2程序总体思路及关键部分:
    2.1粒子系统的框架:
    [2]中已有叙述,这里简要回顾之:

    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’)有如下计算公式:

    图1

    2.3.1抛体运动模式关键:
    抛体运动在这里的右手坐标系(图1)中,是指以y为竖直运方向作上抛运动,x , z方向以固定速度运动的方式。我们在公园里看到的许多喷泉就是这样的例子。
    由于初始化后所有粒子统一生成,所以要看到类似喷泉的效果,需要待几个粒子生成周期之后,生成粒子与死亡粒子可以持续交替时,便会呈现出不错的效果。图2展示了300粒子在程序运行过几个粒子生成周期后的效果。
    相应初始程序、参数及运动函数如下:
    _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螺线极坐标转换成x及z坐标值。再进行相应的更新即可。与前一种不同的是,这里只有y轴方向需要用到vy,而x,z轴的位置则由于采用了坐标控制模式,所以不需要使用vx,vz。为了整体程序设计上的统一,可将vx,vz设定为0。
    与上一个抛物线型运动不同,这里的Fermat螺线运动如果新生粒子与死亡粒子构成了持续生成关系时,将使画面较为混乱。因此,需要将粒子的初始y值及vy,ay设定的靠近些。
    可以看到,团状的粒子簇旋转上升后又扩散下降,无序之中体现着一丝韵律,我将这样的效果命名为“银河落九天”,不知诸位为以为如何?(参图3)
    _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。显然,在这种模式中,没有粒子会死亡。
    _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控制生成高度及速度:
    _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保持上下不同的旋转速度:
    else
    _root.particleArr[i].accTheta(Math.PI/18+0.1*Math.random());

    图5(1000粒)


    3关键代码:
    3.1粒子类
    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);
    &nb,sp; 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初始化函数(以抛物线运动作为示例)
    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主循环函数(以抛物线运动为基础)
    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粒子系统的基本框架并实现了相应的运动效果,如抛体,龙卷风运动等,具有一定的参考意义。并且,对于基于更高级物理效果的运动模拟,如絮流等,作好了准备工作。

  • [] [返回上一页] [打 印] [收 藏]
  • 上一篇教程:猴年画猴:CD11绘制小猴头像(5) 下一篇教程:在Flash动态文本加入省略号
Copyright © 2003-2008 站长助手 www.web162.com
辽ICP备05001760号 网络实名:站长助手 转载本站原创教程请注名来源于本站
力倡站长资源文化,崇尚互联共享,做中国最好站长网站,为中国网站提供动力!
始建于2003-3-1 8:10 中国首都·北京 辽宁(分站)
主资源渠道辽宁电信分公司服务器组及带宽!