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[i]; 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[i].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; } } } [/code] 大概原理就是根据公式遍历舞台上的每个像素点,得到一个计算值,如果该值在指定的阈值之间,就设置为白色。 实心的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及其优化