In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

Pixel Particle Morphing (Radial Mapping)

Click to change image
2つの画像を与えて、その中間画像を生成します (モーフィング)
ピクセル全部をパーティクル化して
適当に対応点を作って適当なトランジション関数に従って動かします
これは、画素の重心からのベクトルを元にそれっぽく対応付けマッピングしてます
トランジションも作ればいろいろと設定できます
同じパーティクルモーフィングでも、マッピングやトランジション次第ではいろいろ形が変わります
しかし、パーティクルベースなのでどうしても色がばらけます
ある程度は平滑化フィルタなりかける事で改善はします(これはしてない)
/**
 * Copyright heriet ( http://wonderfl.net/user/heriet )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/cBbp
 */

// Click to change image
// 
// 2つの画像を与えて、その中間画像を生成します (モーフィング)
//
// ピクセル全部をパーティクル化して
// 適当に対応点を作って適当なトランジション関数に従って動かします
// 
// これは、画素の重心からのベクトルを元にそれっぽく対応付けマッピングしてます
// トランジションも作ればいろいろと設定できます
// 同じパーティクルモーフィングでも、マッピングやトランジション次第ではいろいろ形が変わります
//
// しかし、パーティクルベースなのでどうしても色がばらけます
// ある程度は平滑化フィルタなりかける事で改善はします(これはしてない)
//
 
package  
{
  import flash.display.Bitmap;
  import flash.display.BitmapData;
  import flash.display.Graphics;
  import flash.display.Loader;
  import flash.display.Sprite;
  import flash.display.Shape;
  import flash.filters.ConvolutionFilter;
  import flash.geom.Point;
  import flash.events.Event;
  import flash.events.MouseEvent;
  
  import net.wonderfl.utils.SequentialLoader;
  
  [SWF(width="465", height="465", frameRate = "20", backgroundColor="0xFFFFFF")]
  public class PixelMorphWonderfl extends Sprite
  {
    private static const POINT_ZERO:Point = new Point();
    private static const SOURCE_URL:String = "http://assets.wonderfl.net/images/related_images/8/89/89eb/89ebbef01488d88e537cd4bd36da53a4c971fdd9";
    private static const DESTINATION_URL:String = "http://assets.wonderfl.net/images/related_images/5/5f/5f41/5f41402638adb4c6c95767b2c2ece5cacbd5681f";
    private var _imageArray:Array = [];
    
    private var transitions:Array = [Transitions.LINEAR, Transitions.POW2, Transitions.SINE];
    private var colorSpaces:Array = [ColorSpacePoint.RGB, ColorSpacePoint.LAB, ColorSpacePoint.HLS];
    
    private var source:BitmapData;
    private var destination:BitmapData;
    
    private var morphBitmapData:ParticleMorphBitmapData;
    private var morphBitmap:ParticleMoprphBitmap;
    
    private var sourceGenerator:Sprite;
    private var destinationGenerator:Sprite;
    private var frameCount:int;
    
    private var radialMapping:Radial = new Radial();
    private var interFrames:int;
    
    public function PixelMorphWonderfl() 
    {
      //initialize();
      SequentialLoader.loadImages([SOURCE_URL, DESTINATION_URL], _imageArray, imageLoadedHandler);
      
    }
    private function imageLoadedHandler():void
    {
      var ldr:Loader = _imageArray.pop();
      destination = new BitmapData(ldr.width, ldr.height, true, 0);
      destination.draw(ldr);
      
      ldr = _imageArray.pop();
      source = new BitmapData(ldr.width, ldr.height, true, 0);
      source.draw(ldr);
      
      generateMorphImage();
    }
    private function clickHandler(e:MouseEvent):void
    {
      stage.removeEventListener(MouseEvent.CLICK, clickHandler);
      removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
      initialize();
    }
    public function initialize():void
    {
      while (numChildren > 0) 
        removeChildAt(0);
      
      sourceGenerator = new Sprite();
      destinationGenerator = new Sprite();
      
      if (source != null)
        source.dispose();
      if (destination != null)
        destination.dispose();
      if (morphBitmapData != null)
        morphBitmapData.dispose();
      
      source = new BitmapData(96, 96, true, 0);
      destination = new BitmapData(96, 96, true, 0);
      
      generateRandomGraphics(sourceGenerator.graphics);
      generateRandomGraphics(destinationGenerator.graphics);
      frameCount = 0;
      
      addEventListener(Event.ENTER_FRAME, waitFrame);
    }
    // tekito-
    private function generateRandomGraphics(g:Graphics):void
    {
      var i:int;
      var n:int;
      
      n = (int) (Math.random() * 4) + 1;
      
      for (i = 0; i < n; i++ ){
        g.beginFill(generateColor());
        g.drawEllipse((int) (Math.random() * 24 + 8), (int) (Math.random() * 24 + 8), (int) (Math.random() * 48 + 5), (int) (Math.random() * 48 + 5));
        g.endFill();
      }
      
      n = (int) (Math.random() * 4) + 1;
      for (i = 0; i < n; i++ ){
        g.beginFill(generateColor());
        g.drawRect((int) (Math.random() * 24 + 8), (int) (Math.random() * 24 + 8), (int) (Math.random() * 50 + 5), (int) (Math.random() * 48 + 5));
        g.endFill();
      }
      
      n = (int) (Math.random() * 4) + 1;
      for (i = 0; i < n; i++ ){
        g.beginFill(generateColor());
        var cx:int = (int) (Math.random() * 24 + 8);
        var cy:int = (int) (Math.random() * 24 + 8);
        var sx:int = (int) (Math.random() * 64);
        var sy:int = (int) (Math.random() * 64);
        g.moveTo(sx + cx, sy + cy);
        
        var m:int = (int) (Math.random() * 3) + 2;
        for (var j:int = 0; j < m;  j++){
          var dx:int = (int) (Math.random() * 32);
          var dy:int = (int) (Math.random() * 32);
          g.lineTo(dx + cx, dy + cy);
        }
        g.lineTo(sx + cx, sy + cy);
        g.endFill();
      }
    }
    private function generateColor():uint
    {
      var red:int   = Math.random() * 64 + 196;
      var green:int = Math.random() * 64 + 196;
      var blue:int  = Math.random() * 64 + 196;
      
      return (red << 16) + (green << 8) + blue;
    }
    // wait 2 frames for imageGenerator
    private function waitFrame(e:Event):void
    {
      removeEventListener(Event.ENTER_FRAME, waitFrame);
      addEventListener(Event.ENTER_FRAME, generateImage);
    }
    private function generateImage(e:Event):void
    {
      removeEventListener(Event.ENTER_FRAME, generateImage);
      
      source.draw(sourceGenerator);
      destination.draw(destinationGenerator);
      source.threshold(source, source.rect, POINT_ZERO, '!=', 0xFF000000, 0, 0xFF000000);
      destination.threshold(destination, destination.rect, POINT_ZERO, '!=', 0xFF000000, 0, 0xFF000000);
      
      generateMorphImage();
    }
    private function generateMorphImage():void
    {
      var myBitmap:Bitmap = new Bitmap(source);
      addChild(myBitmap);
      myBitmap.scaleX = myBitmap.scaleY = 2;
      myBitmap.x = 32;
      myBitmap.y = 8;
      
      myBitmap = new Bitmap(destination);
      addChild(myBitmap);
      myBitmap.scaleX = myBitmap.scaleY = 2;
      myBitmap.x = (32 + 96)*2;
      myBitmap.y = 8;
      
      /* Main routine */
      
      interFrames = Math.random() * 8 + 8;
      radialMapping.moveTransition = transitions[Math.random() * 1000 % transitions.length];
      radialMapping.colorTransition = transitions[Math.random() * 1000 % transitions.length];
      radialMapping.colorSpace = colorSpaces[Math.random() * 1000 % colorSpaces.length];
      
      // create morphing bitmapData source to destination
      morphBitmapData = new ParticleMorphBitmapData(source, destination, interFrames, radialMapping);
      morphBitmap = new ParticleMoprphBitmap(morphBitmapData);
      
      addChild(morphBitmap);
      morphBitmap.x = 32;
      morphBitmap.y = 150;
      morphBitmap.scaleX = morphBitmap.scaleY = 4;
      
      addEventListener(Event.ENTER_FRAME, filtering);
    }
    // smoothing bitmapData
    private function filtering(e:Event):void
    {
      removeEventListener(Event.ENTER_FRAME, filtering);
      
      // very slow
      // you can use other filter (e.g. Convolution Filter)
      /*
      var buffer:BitmapData = new BitmapData(96, 96, true, 0);
      
      var n:int = morphBitmap.bitmapDataList.length;
      var repeat:int = Math.random() * 100 % 3;
      
      for (var j:int = 0; j < repeat; j++ ) {
        for (var i:int = 0; i < n; i++) {
          var bitmapData:BitmapData = morphBitmap.bitmapDataList[i];
          smooth(bitmapData, buffer);
        }
      }
      buffer.dispose();
      */
      
      addEventListener(Event.ENTER_FRAME, enterFrameHandler);
      stage.addEventListener(MouseEvent.CLICK, clickHandler);
    }
    private function enterFrameHandler(e:Event):void
    {
      if (frameCount < 20)
        ;
      else if (frameCount <= 20 + interFrames) {
        morphBitmap.nextFrame();
      }
      else if (frameCount < 40 + interFrames)
        ;
      else {
        morphBitmap.prevFrame();
      }
      
      if (frameCount > 40 + interFrames * 2)
        frameCount = 0;
        
      frameCount++;
    }
    public function smooth(source:BitmapData, buffer:BitmapData):void
    {
      buffer.fillRect(buffer.rect, 0);
      buffer.copyPixels(source, source.rect, POINT_ZERO, null, null, true);
      
      for (var y:int = 0; y < buffer.height; y++ ) {
        for (var x:int = 0; x < buffer.width; x++ ) {
          if (source.getPixel32(x, y) == 0) {
            smoothPoint(source, buffer, x, y);
          }
        }
      }
      source.fillRect(source.rect, 0);
      source.copyPixels(buffer, buffer.rect, POINT_ZERO, null, null, true);
    }
    public function smoothPoint(source:BitmapData, buffer:BitmapData, cx:int, cy:int):void
    {
      var n:int = 0;
      var alpha:uint;
      var red:int;
      var green:int;
      var blue:int;
      var colorList:Vector.<ColorSpacePoint> = new Vector.<ColorSpacePoint>();
      
      for (var iy:int = -1; iy <= 1; iy++){
        for (var ix:int = -1; ix <= 1; ix++) {
          var x:int = cx + ix;
          var y:int = cy + iy;
          if ((ix == 0 && iy == 0) || x < 0 || buffer.width <= x || y < 0 || buffer.height <= y )
            continue;
          var color:uint = source.getPixel32(x, y);
          alpha += (color & 0xFF000000) >>> 24;
          red += (color & 0xFF0000) >> 16;
          green += (color & 0xFF00) >> 8;
          blue += color & 0xFF;
          colorList.push(new ColorSpacePoint(color));
          n++;
        }
      }
      
      if (n > 0) {
        alpha /= n;
        if (alpha < 128)
          return
        
        red /= n;
        green /= n;
        blue /= n;
        color = (red << 16) + (green << 8) + blue;
        
        var blendColor:ColorSpacePoint = new ColorSpacePoint(color);
        blendColor.calcLab();
        
        var cd:ColorSpacePoint = colorList[0];
        cd.calcLab();
        var minLength:Number = cd.labLength(blendColor);
        var minIndex:int = 0;
        
        for (var i:int = 1; i < n; i++ )
        {
          cd = colorList[i];
          cd.calcLab();
          var length:Number = cd.labLength(blendColor);
          if (length < minLength) {
            minLength = length;
            minIndex = i;
          }
        }
        cd = colorList[minIndex];
        buffer.setPixel32(cx, cy, cd.color);
      }
    }
  }
}

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.geom.Point;

class ColorSpacePoint
{
  public static const RGB:String = 'rgb';
  public static const HLS:String = 'hls';
  public static const LAB:String = 'lab';
  
  public var color:uint;
    
  public var alpha:int;
  public var red:int;
  public var green:int;
  public var blue:int;
  
  public var l:Number;
  public var a:Number;
  public var b:Number;
  
  public var hue:int;
  public var lightness:Number;
  public var saturation:Number;
  public var value:Number;
  
  
  public function ColorSpacePoint(color:uint) 
  {
    this.color = color;
    
    alpha = (color & 0xFF000000) >>> 24;
    red = (color & 0xFF0000) >>> 16;
    green = (color & 0xFF00) >>> 8;
    blue = (color & 0xFF);
  }
  public function blendRGB(t:Number, transition:ITransition, blendColor:ColorSpacePoint):uint
  {
    var blendRed:int = limitInt(transition.calculate(t, red, blendColor.red), 0, 255);
    var blendGreen:int = limitInt(transition.calculate(t, green, blendColor.green), 0, 255);
    var blendBlue:int = limitInt(transition.calculate(t, blue, blendColor.blue), 0, 255);
    
    return (alpha << 24) + (blendRed << 16) + (blendGreen << 8) + blendBlue;
  }
  public function blendLab(t:Number, transition:ITransition, blendColor:ColorSpacePoint):uint
  {
    var blendL:Number = limitNumber(transition.calculate(t, l, blendColor.l), 0, 100);
    var blendA:Number = limitNumber(transition.calculate(t, a, blendColor.a), -134, 220);
    var blendB:Number = limitNumber(transition.calculate(t, b, blendColor.b), -140, 122);
    
    return (alpha << 24) + getLabToRGB(blendL, blendA, blendB);
  }
  public function getLabToRGB(l:Number, a:Number, b:Number):uint
  {
    var yr:Number = l > 903.3 * 0.008856 ? Math.pow((l + 16) / 116, 3) : l / 903.3;
    var fy:Number = yr > 0.008856 ? (l + 16) / 116 : (903.3 * yr + 16) / 116;
    var fx:Number = a / 500 + fy;
    var fz:Number = fy - b / 200;
    var fx3:Number = Math.pow(fx, 3);
    var fz3:Number = Math.pow(fz, 3);
    var xr:Number = fx3 > 0.008856 ? fx3 : (116 * fx - 16) / 903.3;
    var zr:Number = fz3 > 0.008856 ? fz3 : (116 * fz - 16) / 903.3;
    var x:Number = xr * 0.95045;
    var y:Number = yr;
    var z:Number = zr * 1.08892;
    
    var myRed:int = limitInt((3.240479 * x - 1.53715 * y - 0.498535 * z) * 255, 0, 255);
    var myGreen:int = limitInt((-0.969256 * x + 1.875991 * y + 0.041556 * z) * 255, 0, 255);
    var myBlue:int = limitInt((0.055648 * x - 0.204043 * y + 1.057311 * z)* 255, 0, 255); 
    
    return (myRed << 16) + (myGreen << 8) + myBlue;
  }
  public function calcLab():void
  {
    var x:Number = 0.412391 * red/255 + 0.357584 * green/255 + 0.180481 * blue/255;
    var y:Number = 0.212639 * red/255 + 0.715169 * green/255 + 0.072192 * blue/255;
    var z:Number = 0.019331 * red/255 + 0.119195 * green/255 + 0.950532 * blue/255;
    
    var fx:Number = f(x / 0.95045);
    var fy:Number = f(y);
    var fz:Number = f(z / 1.08892);
    
    l = 116 * fy - 16;
    a = 500 * (fx - fy);
    b = 200 * (fy - fz);
    
    function f(value:Number):Number
    {
      return value > 0.008856 ? Math.pow(value, 1 / 3) : (903.3 * value + 16) / 116;
    }
  }
  public function blendHLS(t:Number, transition:ITransition, blendColor:ColorSpacePoint):uint
  {
      
    var blendH:int; 
    if (Math.abs(hue - blendColor.hue) < 180)
      blendH = transition.calculate(t, hue, blendColor.hue) % 360;
    else
      blendH = (transition.calculate(t, (hue + 180) % 360, (blendColor.hue + 180) % 360) + 180)  % 360;
    var blendL:Number = transition.calculate(t, lightness, blendColor.lightness);
    var blendS:Number = transition.calculate(t, saturation, blendColor.saturation);
    
    return (alpha << 24) + getHLSToRGB(blendH, blendL, blendS);
  }
  public function getHLSToRGB(h:int, l:Number, s:Number):uint
  {
    var myRed:int, myGreen:int, myBlue:int;
    var maxR:Number = l <= 0.5 ? l * (1 + s) : l * (1 - s) + s;
    var minR:Number = 2 * l - maxR;
    
    if (s == 0){
      myRed = myGreen = myBlue = lightness * 255;
    }
    else {
      var hk:Number = h / 360;
      var tr:Number = hk + 1 / 3;
      var tg:Number = hk;
      var tb:Number = hk - 1 / 3;
      
      if (tr < 0) tr += 1.0;
      if (tg < 0) tg += 1.0;
      if (tb < 0) tb += 1.0;
      
      if (tr > 1) tr -= 1.0;
      if (tg > 1) tg -= 1.0;
      if (tb > 1) tb -= 1.0;
      
      myRed = limitInt(funcT(minR, maxR, tr)*255, 0, 255);
      myGreen = limitInt(funcT(minR, maxR, tg)*255, 0, 255);
      myBlue = limitInt(funcT(minR, maxR, tb)*255, 0, 255);  
    }
    
    return (myRed << 16) + (myGreen << 8) + myBlue;
    
    function funcT(p:Number, q:Number, t:Number):Number
    {
      if (t < 1 / 6)
        return p +((q - p) * 6 * t);
      else if (t < 1 / 2)
        return q;
      else if (t < 2 / 3)
        return p + ((q - p) * (4 - 6*t));
      else
        return p;
    }
  }
  public function calcHLS():void
  {
    var max:int = red;
    var min:int = red;
    
    if (max < green)
      max = green;
    if (max < blue)
      max = blue;
      
    if (green < min)
      min = green;
    if (blue < min)
      min = blue;
    
    var maxR:Number = max / 255;
    var minR:Number = min / 255;
    
    lightness = (maxR + minR) / 2;
    
    if (max == min) {
      saturation = 0;
      hue = 0;
    }
    else {
      saturation = lightness <= 0.5 ? (maxR - minR) / (maxR + minR) : (maxR - minR) / (2 - maxR - minR);
      var cr:Number = (maxR - red / 255) / (maxR - minR);
      var cg:Number = (maxR - green / 255) / (maxR - minR);
      var cb:Number = (maxR - blue / 255) / (maxR - minR);
      
      if (red == max)
        hue = (cb - cg) * 60;
      else if (green == max)
        hue = (2 + cr - cb) * 60;
      else
        hue = (4 + cg - cr) * 60;
      
      hue = (hue + 360) % 360;
    }
  }
  public function labLength(cd:ColorSpacePoint):Number
  {
    return Math.pow(l - cd.l, 2) + Math.pow(b - cd.b, 2) + Math.pow(b - cd.b, 2);
  }
  private function limitInt(value:int, min:int, max:int):int
  {
    if (value < min)
      value = min;
    else if (value > max)
      value = max;
    return value;
  }
  private function limitNumber(value:Number, min:Number, max:Number):Number
  {
    if (value < min)
      value = min;
    else if (value > max)
      value = max;
    return value;
  }
}
  
class Particle 
{
  public var fromX:int;
  public var fromY:int;
  public var toX:int;
  public var toY:int;
  public var length:int;
  public var degree:int;
  public var setted:Boolean;
    
  public var fromColor:ColorSpacePoint;
  public var toColor:ColorSpacePoint;
  public var moveTransition:ITransition = Transitions.LINEAR;
  public var colorTransition:ITransition = Transitions.LINEAR;
  public var colorSpace:String = ColorSpacePoint.RGB;
    
  public function Particle(fromX:int = 0, fromY:int = 0, fromColor:uint = 0) 
  {
    this.fromX = fromX;
    this.fromY = fromY;
    this.fromColor = new ColorSpacePoint(fromColor);
  }
  public function draw(buffer:BitmapData, time:Number):void
  {
    var x:int = moveTransition.calculate(time, fromX, toX);
    var y:int = moveTransition.calculate(time, fromY, toY);
    var color:uint;
    
    if (colorSpace == ColorSpacePoint.RGB)
      color = fromColor.blendRGB(time, colorTransition, toColor);
    else if (colorSpace == ColorSpacePoint.LAB)
      color = fromColor.blendLab(time, colorTransition, toColor);
    else if (colorSpace == ColorSpacePoint.HLS)
      color = fromColor.blendHLS(time, colorTransition, toColor);
    
    buffer.setPixel32(x, y, color);
  }
  public function setMap(p:Particle, moveTransition:ITransition = null, colorTransition:ITransition = null, colorSpace:String = null):void
  {
    setted = true;
    toX = p.fromX;
    toY = p.fromY;
    toColor = p.fromColor;
    
    if(moveTransition != null)
      this.moveTransition = moveTransition;
    if(colorTransition != null)
      this.colorTransition = colorTransition;
    if (colorSpace != null)
      this.colorSpace = colorSpace;
      
    if (colorSpace == ColorSpacePoint.LAB){
      fromColor.calcLab();
    }
    else if (colorSpace == ColorSpacePoint.HLS) {
      fromColor.calcHLS();
    }
  }
}
class Mass
{
  public var centerX:int;
  public var centerY:int;
  public var particleList:Vector.<Particle> = new Vector.<Particle>();
  public var paletteList:Vector.<uint>;
    
  public function Mass(bitmapData:BitmapData, paletteList:Vector.<uint> = null, backgroundColor:uint = 0) 
  {
    this.paletteList = paletteList;
      
    var x:int, y:int;
    var width:int = bitmapData.width;
    var height:int = bitmapData.height;
      
    for (y = 0; y < height; y++ ) {
      for (x = 0; x < width; x++ ) {
        var color:uint = bitmapData.getPixel32(x, y);
        if (color != backgroundColor) {
          var particle:Particle = new Particle(x, y, color);
          particleList.push(particle);
          centerX += x;
          centerY += y;
        }
      }
    }
    
    var n:int = particleList.length;
    if (n != 0) {
      centerX /= n;
      centerY /= n;
    }
  }
}
class ParticleMorphBitmapData extends BitmapData
{
  private static const POINT_ZERO:Point = new Point();
  
  private var _source:BitmapData;
  private var _destination:BitmapData;  
  private var _sourceMass:Mass;
  private var _destinationMass:Mass;
    
  private var _interFrames:int; // inter frame num. total frames = 1 + inter frames + 1
  public function get interFrames():int { return _interFrames }
  public function get totalFrames():int { return _interFrames + 2 }
    
  private var _rate:Number;
  private var _currentFrame:int;
  public function get currentFrame():int { return _currentFrame }
  public function set currentFrame(value:int):void { _currentFrame = value }
    
  private var _smoothBuffer:BitmapData;
  private var _mapping:IMapping = Mappings.RADIAL;
    
    
  public function ParticleMorphBitmapData(source:BitmapData, destination:BitmapData, interFrames:int = 1,mapping:IMapping = null)
  {
    var maxWidth:int = source.width < destination.width ? destination.width : source.width;
    var maxHeight:int = source.height < destination.height ? destination.height : source.height;
      
    super(maxWidth, maxHeight, true, 0);
      
    _smoothBuffer = new BitmapData(maxWidth, maxHeight, true, 0);
      
    if(mapping != null)
      _mapping = mapping;
      
    _source = source;
    _destination = destination;
    _interFrames = interFrames;
    _rate = 1.0 / (interFrames + 1);
    _currentFrame = 0;
      
    _sourceMass = new Mass(_source);
    _destinationMass = new Mass(_destination);
    
    _mapping.map(_sourceMass, _destinationMass);
  }
  public function update():void
  {
    lock();
      
    this.fillRect(this.rect, 0);
      
    if (_currentFrame <= 0)
      this.copyPixels(_source, _source.rect, POINT_ZERO, null, null, true);
    else if (_currentFrame >= _interFrames + 1)
      this.copyPixels(_destination, _destination.rect, POINT_ZERO, null, null, true);
    else 
      drawFrame(_currentFrame, this);
    
    unlock();
  }
  public function nextFrame():void
  {
    _currentFrame++;
    update();
  }
  public function prevFrame():void
  {
    _currentFrame--;
    update();
  }
  public function drawFrame(frame:int, buffer:BitmapData):void
  {
    if (frame <= 0) {
      buffer.copyPixels(_source, _source.rect, POINT_ZERO, null, null, true);
    }
    else if (frame >= _interFrames + 1) {
      buffer.copyPixels(_destination, _destination.rect, POINT_ZERO, null, null, true);
    }
    else {
      var t:Number = _rate * frame;
      drawMass(t, _sourceMass, buffer);
      drawMass(1-t, _destinationMass, buffer);
    }
  }
  public function drawMass(t:Number, mass:Mass, buffer:BitmapData):void
  {
    var particleList:Vector.<Particle> = mass.particleList;
    var n:int = particleList.length;
    for (var i:int = 0; i < n; i++ )
    {
      var particle:Particle = particleList[i];
      particle.draw(buffer, t);
    }
  }
  override public function dispose():void 
  {
    _smoothBuffer.dispose();
    super.dispose();
  }
}

class ParticleMoprphBitmap extends Bitmap
{
  private var _totalFrames:int;
  private var _currentFrame:int;
  public function get currentFrame():int { return _currentFrame }
    
  private var _bitmapDataList:Vector.<BitmapData>;
  public function get bitmapDataList():Vector.<BitmapData> { return _bitmapDataList }
    
  private var _morphData:ParticleMorphBitmapData;
  public function get morphData():ParticleMorphBitmapData { return _morphData }
    
  public function ParticleMoprphBitmap(morphData:ParticleMorphBitmapData = null) 
  {
    super();
    
    _currentFrame = 0;
    _bitmapDataList = new Vector.<BitmapData>();
    
    this.morphData = morphData;
  }
  public function set morphData(value:ParticleMorphBitmapData):void
  {
    if (value == null)
      return;
      
    _morphData = value;
    disposeBitmapDataList();
      
    _morphData.currentFrame = 0;
    _morphData.update();
      
    var zeroPoint:Point = new Point();
      
    _totalFrames = _morphData.totalFrames;
    for (var i:int = 0; i < _totalFrames; i++ ) {
      var myBitmapData:BitmapData = new BitmapData(_morphData.width, _morphData.height, true, 0);
      myBitmapData.copyPixels(_morphData, _morphData.rect, zeroPoint, null, null, true);
      _bitmapDataList.push(myBitmapData);
      _morphData.nextFrame();
    }
    
    bitmapData = _bitmapDataList[_currentFrame];
  }
  public function nextFrame():void
  {
    _currentFrame++;
    if (_totalFrames <= _currentFrame)
      _currentFrame = _totalFrames - 1;
    
    bitmapData = _bitmapDataList[_currentFrame];
  }
  public function prevFrame():void
  {
    _currentFrame--;
    if (_currentFrame < 0)
      _currentFrame = 0;
    
    bitmapData = _bitmapDataList[_currentFrame];
  }
  private function disposeBitmapDataList():void
  {
    var n:int = _bitmapDataList.length;
    for (var i:int = 0; i < n; i++) {
      var bitmapData:BitmapData = _bitmapDataList[i];
      bitmapData.dispose();
    }
    _bitmapDataList.splice(0, n);
  }
}

interface ITransition 
{
  function calculate(t:Number, from:Number, to:Number):Number;
}
class Linear implements ITransition
{
  public function calculate(t:Number, from:Number, to:Number):Number
  {
    return (to-from) * t + from;
  }  
}
class Pow implements ITransition
{
  public var dimension:Number;
  
  public function Pow(dimension:Number = 2.0) 
  {
    this.dimension = dimension;
  }
  public function calculate(t:Number, from:Number, to:Number):Number
  {
    return (to-from) * Math.pow(t, dimension) + from;
  }  
}
class Sine implements ITransition
{
  public var frequency:int;
  
  public function Sine(frequency:int = 0)
  {
    this.frequency = frequency;
  }
  public function calculate(t:Number, from:Number, to:Number):Number
  {
    return (to-from) * Math.sin(t * (1/2 + 2 * frequency) * Math.PI) + from;
  }  
}
class Transitions 
{
  public static const LINEAR:Linear = new Linear();
  public static const POW2:Pow = new Pow(2);
  public static const SINE:Sine = new Sine();
}

interface IMapping 
{
  function get moveTransition():ITransition;
  function set moveTransition(value:ITransition):void;
  function get colorTransition():ITransition;
  function set colorTransition(value:ITransition):void;
  function get colorSpace():String;
  function set colorSpace(value:String):void;
  function map(m1:Mass, m2:Mass):void;
}
class MappingBase implements IMapping
  {
  public function get moveTransition():ITransition { return _moveTransition; }
  public function set moveTransition(value:ITransition):void { _moveTransition = value; }
  public function get colorTransition():ITransition { return _colorTransition; }
  public function set colorTransition(value:ITransition):void { _colorTransition = value; }
  public function get colorSpace():String { return _colorSpace; }
  public function set colorSpace(value:String):void { _colorSpace = value; }
    
  protected var _moveTransition:ITransition;
  protected var _colorTransition:ITransition;
  protected var _colorSpace:String;
    
  public function MappingBase(moveTransition:ITransition = null, colorTransition:ITransition = null, colorSpace:String = null)
  {
    _moveTransition = moveTransition;
    _colorTransition = colorTransition;
    _colorSpace = colorSpace;
  }
  public function map(m1:Mass, m2:Mass):void
  {
  }
}
class Radial extends MappingBase
{
  public var deg:int;
    
  public function Radial(moveTransition:ITransition = null, colorTransition:ITransition = null, colorSpace:String = null, deg:int = 1) 
  {
    super(moveTransition, colorTransition, colorSpace);
    
    this.deg = deg;
  }
  override public function map(m1:Mass, m2:Mass):void
  {
    sortDeg(m1);
    sortDeg(m2);
      
    var p1:Vector.<Particle> = m1.particleList;
    var n:int = p1.length;
    var p2:Vector.<Particle> = m2.particleList;
    var m:int = p2.length;
      
    var v1:Vector.<Particle> = new Vector.<Particle>();
    var v2:Vector.<Particle> = new Vector.<Particle>();
    var i1:int = 0;
    var i2:int = 0;
      
    for (var d:int = 0; d < 360; d+=deg) {
      while (i1 < n && p1[i1].degree <= d){
        v1.push(p1[i1]);
        i1++;
      }
      while (i2 < m && p2[i2].degree <= d){
        v2.push(p2[i2]);
        i2++;
      }
      if (i1 == n){
        while (i2 < m) {
          v2.push(p2[i2]);
          i2++;
        }
      }
      if (i2  == m) {
        while (i1 < n) {
          v1.push(p1[i1]);
          i1++;
        }
      }
      
      var v1n:int = v1.length;
      var v2n:int = v2.length;
        
      if (v1n > 0 && v2n > 0) {
        if (v1n > v2n)
          mapPart(v1, v2, v1n, v2n);
        else
          mapPart(v2, v1, v2n, v1n);
        
        v1.splice(0, v1n);
        v2.splice(0, v2n);
      }
    }
  }
  // v1.num > v2.num
  private function mapPart(v1:Vector.<Particle>, v2:Vector.<Particle>, v1n:int, v2n:int):void
  {
    var unit:Number = v1n / v2n;
    var i1:int = 0;
    var i2:int = 0;
    
    for (i2 = 0; i2 < v2n; i2++ )
    {
      var p2:Particle = v2[i2];
    
      while (i1 < unit * (i2 + 1) && i1 < v1n) {
        var p1:Particle = v1[i1];
        p1.setMap(p2, _moveTransition, _colorTransition, _colorSpace);
        p2.setMap(p1, _moveTransition, _colorTransition, _colorSpace);
        i1++;
      }
    }
  }
  
  private function sortDeg(mass:Mass):void
  {
    var particleList:Vector.<Particle> = mass.particleList;
    var n:int = particleList.length;
    for (var i:int = 0; i < n; i++ )
    {
      var particle:Particle = particleList[i];
      var x:Number = particle.fromX + 0.5;
      var y:Number = particle.fromY + 0.5;
      particle.length = Math.pow(x - mass.centerX, 2) + Math.pow(y - mass.centerY, 2);
      particle.degree = Math.floor(((Math.atan2(y - mass.centerY, x - mass.centerX) * 180 / Math.PI) + 90)) % 360
    }
    particleList.sort(compareDeg)
    
    function compareDeg(p1:Particle, p2:Particle):Number
    {
      if (p1.degree < p2.degree)
        return -1;
      else if (p1.degree > p2.degree)
        return 1;
      else if (p1.fromColor.color < p2.fromColor.color)
        return -1;
      else if (p1.fromColor.color > p2.fromColor.color)
        return 1;
      else if (p1.length < p2.length)
        return -1;
      else if (p1.length > p2.length)
        return 1;
      else
        return 0;
    }
  }
}
class Mappings
{
  public static const RADIAL:Radial = new Radial();
}