基本Metaball及其优化

Metaball everyinch 6703℃ 0评论

Metaball(元球)曲面是一种特殊的隐函数曲面。它采用具有等势场值的点集来定义曲面,因此,Metaball曲面实际上是一张等势面。Metaball具有相互靠近到一定距离产生变形,再进一步靠近时融合成光滑表面的特性。多个Metaball能够生成非常复杂的形状,比如人的肌肉、胃、肠等。
二维Metaball的公式为:

其中,x、y是舞台上的任意一个点,x0、y0是metaball的位置,R为半径。
创建一个基本的Metaball类,命名为BasicMetaball.as

package{
	public class BasicMetaball{
		public var radius:Number;
		public var x:int;
		public var y:int;
		public var original_radius:Number;
		public var vx:Number;
		public var vy:Number;

		public function BasicMetaball(x:Number, y:Number, radius:Number){
			this.x=x;
			this.y=y;
			this.original_radius=radius;
			this.radius=radius * radius; //为了优化方程,保存半径的平方  
		}

		public function equation(tx:Number, ty:Number):Number{
			//Metaball的方程
			return radius / ((x - tx) * (x - tx) + (y - ty) * (y - ty));  
		}
	}
}

接下来做一个测试类MetaballTest1.as

package{
	import flash.display.Sprite;
	import flash.display.BitmapData;
	import flash.display.Bitmap;
	import flash.events.Event;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.filters.BlurFilter;
	
	[SWF(width="320",height="240")]
	public class MetaballTest1 extends Sprite{
		private var sW:Number=320;//舞台宽度
		private var sH:Number=240;//舞台高度
		
		private var bmp:Bitmap = new Bitmap();//最终用来显示的位图对象
		private var bmpd:BitmapData=new BitmapData(sW,sH,false,0xff000000);//默认生成一个黑背景的BitmapData
		private var rect:Rectangle=bmpd.rect;//bmpd的矩形区域
		private var pt:Point=new Point(rect.left,rect.top);//rect的左上顶点
		private var blurFilter:BlurFilter=new BlurFilter(10,10);//定义一个模糊滤镜
		
		private var metaballs:Array = new Array();//用于保存n个metaball的数组
		private var n:uint=8;//小球数目
		private var i:uint=0;//循环变量
		private var minThreshold:int=0x000009;//最小阈值
		private var maxThreshold:int=0x000020;//最大阈值

		private var isHollow:Boolean=false;//是否空心图形
		
		public function MetaballTest1(){
			for (i=0; i<n; i++) {
				var b:BasicMetaball=new BasicMetaball(Math.random()*sW,Math.random()*sH,20 + Math.random()*80);
				if (b.x>sW-b.original_radius) {
					b.x=sW-b.original_radius;
				} else if (b.x < b.original_radius) {
					b.x=b.original_radius;
				}
				if (b.y>sH-b.original_radius) {
					b.y=sH-b.original_radius;
				} else if (b.y<b.original_radius) {
					b.y=b.original_radius;
				}
				b.vx = (Math.random()*2-1)*2;
				b.vy = (Math.random()*2-1)*2;
				metaballs.push(b);
			}
			addChild(bmp);
			addEventListener(Event.ENTER_FRAME,onEnterframe);
		}
		
		private function onEnterframe(e:Event):void {
			for (i=0; i<n; i++) {
				var b:BasicMetaball=metaballs&#91;i&#93;;
				b.x+=b.vx;
				b.y+=b.vy;
				
				if (b.x>=sW-b.original_radius) {
					b.x=sW-b.original_radius;
					b.vx*=-1;
				} else if (b.x<b.original_radius) {
					b.x=b.original_radius;
					b.vx*=-1;
				}
		
				if (b.y>=sH-b.original_radius) {
					b.y=sH-b.original_radius;
					b.vy*=-1;
				} else if (b.y<b.original_radius) {
					b.y=b.original_radius;
					b.vy*=-1;
				}
			}
			bmpd.dispose();
			bmpd = new BitmapData(sW,sH,false,0xff000000);
			bmpd.lock();
			bmpd.floodFill(0,0,0);
			var sum:Number=0;
			for (var ty:int = 0; ty < stage.stageHeight; ty++) {
				for (var tx:int = 0; tx < stage.stageWidth; tx++) {
					sum=0;
					for (var i:int = 0; i < metaballs.length; i++) {
						sum+=metaballs&#91;i&#93;.equation(tx,ty);
					}
		
					if (! isHollow) {
						if (sum>=minThreshold) {
							bmpd.setPixel(tx, ty, 0xFFFFFF);
						}
					} else {
						if (sum >= minThreshold && sum <= maxThreshold) {
							bmpd.setPixel(tx, ty, 0xFFFFFF);
						}
					}
				}
			}
			bmpd.applyFilter(bmpd,rect,pt,blurFilter);
			bmpd.unlock();
			bmp.bitmapData=bmpd;
		}
	}
}
&#91;/code&#93;
大概原理就是根据公式遍历舞台上的每个像素点,得到一个计算值,如果该值在指定的阈值之间,就设置为白色。
实心的Metaball
<a href="http://www.everyinch.net/wp-content/uploads/2011/10/MetaballTest1.swf"><img src="http://www.everyinch.net/wp-content/uploads/2011/10/MetaballTest1-300x223.jpg" alt="" title="MetaballTest1" width="300" height="223" class="aligncenter size-medium wp-image-540" /></a>
空心的Metaball
<a href="http://www.everyinch.net/wp-content/uploads/2011/10/MetaballTest21.swf"><img src="http://www.everyinch.net/wp-content/uploads/2011/10/MetaballTest2-300x223.jpg" alt="" title="MetaballTest2" width="300" height="223" class="aligncenter size-medium wp-image-541" /></a>
可以看出示例的运行效率不高,为了提升效率,使用PixelBender来优化效率,PixelBender的脚本如下:

<languageVersion : 1.0;>

kernel Metaballs
<namespace : "Metaballs";
vendor : "Chen Tong";
version : 1;
description : "Two Fast Metaballs";
>
{
parameter float minThreshold
<
minValue:float(0.0);
maxValue:float(2.0);
defaultValue:float(0.9);
description: "minThreshold";
>;
parameter float3 ball1
minValue:float3(0.0,0.0,0.0);
maxValue:float3(640.0,480.0,50.0); //this first is the x, second is y, and the third is radius.
defaultValue:float3(50.0,50.0,20.0);
description: "ball1, params, x,y,radius";
>;

parameter float3 ball2
<
minValue:float3(0.0,0.0,0.0); //storing the data is also odd. We'll use a float3, which is like an array of 3 float values.
maxValue:float3(640.0,480.0,50.0); //this first is the x, second is y, and the third is radius.
defaultValue:float3(50.0,50.0,20.0);
description: "ball2, params, x,y,radius";
>;
  input image4 src;
  output pixel4 dst;

  void evaluatePixel(){
    dst = sampleNearest(src,outCoord());
    dst.rbg = float3(0,0,0);//sets the current pixel to black so the image is cleared before redrawing
    float2 coord = outCoord(); //get the coordinate of the pixel
    float sum = 0.0; //get the sum and set it to 0
    sum += (ball1.z)/((ball1.x-coord.x)*(ball1.x-coord.x)+(ball1.y-coord.y)*(ball1.y-coord.y)); //add to the sum using the formula from the first example
    sum += (ball2.z)/((ball2.x-coord.x)*(ball2.x-coord.x)+(ball2.y-coord.y)*(ball2.y-coord.y)); 
    if(sum >= minThreshold){
        dst.rgb = float3(255,255,255); //set it to black if its within the threshold
    }
  }
}

这时候还需要写一个使Metaball运动及环境边界检测的类Metaball.as

package{
import flash.display.Stage;

public class Metaball extends Object{
public var x:int;
public var y:int;
public var radius:Number;
public var stage:Stage;
private var mass:Number;
private var position:Vector2D;
private var velocity:Vector2D;
private var maxForce:Number;
private var maxSpeed:Number;
private var wanderAngle:Number=0;

public function Metaball(x:Number, y:Number, radius:Number){
this.x=x;
this.y=y;
this.radius=radius * radius;
this.mass=2;
this.position=new Vector2D(x, y);
this.velocity=new Vector2D(Math.random() * 20 - 10, Math.random() * 15 - 7.5);
this.maxForce=20;
this.maxSpeed=10;
}

public function update():void{
velocity.truncate(maxSpeed);
position=position.add(velocity);
if (stage != null){
if (position.x > stage.stageWidth){
position.x=stage.stageWidth;
velocity.x=velocity.x * -1;
}
else if (position.x < 0){ position.x=0; velocity.x=velocity.x * -1; } if (position.y > stage.stageHeight){
position.y=stage.stageHeight;
velocity.y=velocity.y * -1;
}
else if (position.y < 0){ position.y=0; velocity.y=velocity.y * -1; } } x=position.x; y=position.y; } public function equation(tx:Number, ty:Number):Number{ return radius / ((x - tx) * (x - tx) + (y - ty) * (y - ty)); } } } [/code] 最后写一个测试类PixelBenderMetaballs.as [code lang="as3"] package { import flash.display.Sprite; import flash.events.Event; import flash.filters.BlurFilter; import flash.filters.ShaderFilter; import flash.utils.ByteArray; import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.Shader; import flash.display.ShaderPrecision; [SWF(width=640,height=480)] public class PixelBenderMetaballs extends Sprite { [Embed(source='assets/metaball.pbj',mimeType='application/octet-stream')] private static const MetaballsFilter:Class; private const bmpd:BitmapData=new BitmapData(640,480,false,0); private const blur:BlurFilter=new BlurFilter(10,10,1); private var metaballsFilter:ShaderFilter; private var b1:BasicMetaball; private var b2:BasicMetaball; private var bmp:Bitmap; public function PixelBenderMetaballs() { bmp=new Bitmap(bmpd); bmp.smoothing=true; addChild(bmp); var ba:ByteArray = new MetaballsFilter() as ByteArray; var s:Shader=new Shader(ba); metaballsFilter=new ShaderFilter(s); metaballsFilter.shader.data.src.image=bmpd; b1=new BasicMetaball(100,100,Math.random()*20 + 10); b2=new BasicMetaball(150,150,Math.random()*20 + 10); b1.vx = (Math.random()*2-1)*5; b1.vy = (Math.random()*2-1)*5; b2.vx = (Math.random()*2-1)*5; b2.vy = (Math.random()*2-1)*5; metaballsFilter.shader.data.ball1.value=[b1.x,b1.y,b1.radius]; metaballsFilter.shader.data.ball2.value=[b2.x,b2.y,b2.radius]; metaballsFilter.shader.precisionHint=ShaderPrecision.FAST; bmp.filters=[metaballsFilter]; addEventListener(Event.ENTER_FRAME, onEnterframe); } private function onEnterframe(e:Event):void { b1.x+=b1.vx; b1.y+=b1.vy; checkWalls(b1); b2.x += b2.vx; b2.y += b2.vy; checkWalls(b2); metaballsFilter.shader.data.ball1.value=[b1.x,b1.y,b1.radius]; metaballsFilter.shader.data.ball2.value=[b2.x,b2.y,b2.radius]; bmp.filters=[metaballsFilter,blur]; } private function checkWalls(ball:BasicMetaball):void { var sw:Number=stage.stageWidth; var sh:Number=stage.stageHeight; var adjust:uint=5; if (ball.x>sw-ball.original_radius-adjust) {
ball.x=sw-ball.original_radius-adjust;
ball.vx*=-1;
} else if (ball.x < ball.original_radius + adjust) { ball.x=ball.original_radius+adjust; ball.vx*=-1; } if (ball.y>sh-ball.original_radius-adjust) {
ball.y=sh-ball.original_radius-adjust;
ball.vy*=-1;
} else if (ball.y

分享&收藏

转载请注明:陈童的博客 » 基本Metaball及其优化

喜欢 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
'; } if( dopt('d_footcode_b') ) echo dopt('d_footcode'); ?>