Particle repulsion
/**
* Copyright esimov ( http://wonderfl.net/user/esimov )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/tqkY
*/
/**
@author Simo Endre
@wonderfl page http://wonderfl.net/user/esimov/codes
Click on stage to spawn attractive or repulsive balls.
For multiple indentic color balls the algoritm is not implemented correctly at the moment.
*/
package
{
import flash.display.Sprite;
import flash.display.Bitmap;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.display.StageQuality;
import flash.display.BitmapData;
import flash.filters.BlurFilter;
import flash.filters.BitmapFilterQuality;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.display.Shape;
import flash.display.Loader;
import flash.net.URLRequest;
import flash.events.IOErrorEvent;
import flash.display.LoaderInfo;
import flash.display.DisplayObject;
import flash.geom.Rectangle;
import flash.geom.Point;
import flash.geom.ColorTransform;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.net.FileReference;
import flash.net.FileFilter;
import flash.system.LoaderContext;
import flash.system.Security;
import com.bit101.components.RotarySelector;
import com.bit101.components.Style;
import com.bit101.components.Panel;
import com.bit101.components.PushButton;
import net.hires.debug.Stats;
[SWF (backgroundColor = 0x00, width = '465', height = '465')]
public class ParticleFusion extends Sprite
{
// CONSTANTS
private static const SPRING:Number = 0.01;
private static const BOUNCE:Number = 0.998;
private static const REPEL:Number = -0.38;
private static const DAMP:Number = 0.97;
private static const K:Number = 18;
private const OFFSET_X:Number = 50;
private const OFFSET_Y:Number = 45;
private const WIDTH:Number = stage.stageWidth;
private const HEIGHT:Number = stage.stageHeight;
// Particle attributes
private var _maxDistSQ:Number = Math.pow(1000 / Math.sqrt(2), 2);
private var _minDistForce:Number = 2000;
private var _lastMouseX:Number;
private var _lastMouseY:Number;
private var _dx:Number;
private var _dy:Number;
private var _vdx:Number;
private var _vdy:Number;
private var _firstParticle:Particle;
// Stage and Bitmap variables
private var _stat:Stats;
private var _stage:Sprite = new Sprite();
private var _bmpData:BitmapData = new BitmapData(WIDTH, HEIGHT, true, 0x00000000);
private var _bmp:Bitmap = new Bitmap(_bmpData);
private var _blur:BlurFilter = new BlurFilter(3,3);
private var _circle:Circle;
private var _redCircles:Object = new Object();
private var _greenCircles:Object = new Object();
private var _blueCircles:Object = new Object();
private var _numCircles:Number = 10;
private var _count:Number = 0; // count the spawn circles
private var _particles:TextField;
private var _file:FileReference;
// Control variables
private var _panel:Panel;
private var _springs:RotarySelector;
private var _sinks:RotarySelector;
private var _manual:RotarySelector;
private var _loadFile:PushButton;
//Boolean variables
private var _springsOn:Boolean = false;
private var _sinksOn:Boolean = false;
private var _manualOn:Boolean = false;
private var _mouseDown:Boolean = false;
public function ParticleFusion():void
{
if (stage) { initStage(null) }
else
{
addEventListener(Event.ADDED_TO_STAGE, initStage);
throw new Error("Stage not active");
}
}
private function initStage(event:Event):void
{
Wonderfl.capture_delay (10);
removeEventListener(Event.ADDED_TO_STAGE, initStage);
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.quality = StageQuality.MEDIUM;
stage.frameRate = 30;
_blur.quality = BitmapFilterQuality.LOW;
addChild(_stage);
addChild(_stat = new Stats());
var tf:TextFormat = new TextFormat("Verdana", 10);
_particles = new TextField();
_particles.defaultTextFormat = tf;
_particles.background = true;
_particles.backgroundColor = 0x333333;
_particles.autoSize = "right";
_particles.textColor = 0xffffff;
_particles.x = WIDTH - _particles.textWidth - 5;
_particles.text = "";
addChild(_particles);
_stage.addChild(_bmp);
//Style.setStyle(Style.DARK);
_panel = new Panel(_stage, 0, HEIGHT - OFFSET_Y);
_panel.width = WIDTH;
var offset_x:Number = OFFSET_X / 2 ;
_springs = new RotarySelector(_panel, offset_x, 5, "Springs", changeSpringSelection);
_springs.height = 20;
_springs.numChoices = 2;
_springs.labelMode = RotarySelector.NUMERIC;
offset_x = _springs.x + _springs.width + OFFSET_X;
_sinks = new RotarySelector(_panel, offset_x, 5, "Sinks Auto", changeSinkSelection);
_sinks.height = 20;
_sinks.numChoices = 2;
_sinks.labelMode = RotarySelector.NUMERIC;
offset_x = _sinks.x + _sinks.width + OFFSET_X;
_manual = new RotarySelector(_panel, offset_x, 5, "Sinks Manual", changeManualSelection);
offset_x = _manual.x + _manual.width + OFFSET_X;
_manual.height = 20;
_manual.numChoices = 2;
_manual.labelMode = RotarySelector.NUMERIC;
_loadFile = new PushButton(_panel, offset_x, 5, "Load Image", loadImage);
_springs.enabled = false;
_sinks.enabled = false;
_manual.enabled = false;
Security.allowDomain("wonderfl.net");
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadImage);
loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, IOErrorListener);
loader.load(new URLRequest("http://assets.wonderfl.net/images/related_images/b/b4/b421/b421fb944250e4770d6df0bde5cfbfa40ae81bf2"), new LoaderContext(true));
addEventListener(Event.ENTER_FRAME, startEvent);
}
private function changeManualSelection (e:Event):void
{
_manualOn = !_manualOn;
}
private function changeSinkSelection (e:Event):void
{
_sinksOn = !_sinksOn;
}
private function changeSpringSelection (e:Event):void
{
_springsOn = !_springsOn;
}
private function loadImage(event:Event):void
{
_file = new FileReference();
_file.browse([new FileFilter("Image Files", "*.jpg; *.jpeg; *.png; *.bmp; *.tiff", null),
new FileFilter("All Images", "*.*")]);
_file.addEventListener(Event.SELECT, fileLoadSelect);
}
private function fileLoadSelect(event:Event):void
{
_file.load();
_file.addEventListener(Event.COMPLETE, fileLoadComplete);
}
private function fileLoadComplete(event:Event):void
{
var loader:Loader = new Loader();
loader.loadBytes(_file.data);
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadImage);
}
private function onMouseDown (event:MouseEvent):void
{
if (!_manualOn)
{
var rect:Rectangle = _panel.getBounds(this);
if (mouseY < rect.top)
{
if (_count <= _numCircles)
{
var randomColor:Number = Math.abs(Math.round(Math.random() * 10 - 2) + Math.round(Math.random() * 10 - 4));
var dir_x:Number = Math.random() * 8.5 - 2.5;
var dir_y:Number = Math.random() * 8.5 - 2.5;
if (randomColor >= 0 && randomColor <= 4)
{
var red:Circle = new Circle(6, 3, 0xff1111);
red.x = mouseX;
red.y = mouseY;
_redCircles["red"+ _count++] = {x:mouseX, y:mouseY, ref:red, radius:red.radius, lastX: Number, lastY:Number,
vel_x:Math.cos(dir_x) * 8.5, vel_y:Math.sin(dir_y) * 8.5}
red.mouseEnabled = false;
_stage.addChild(red);
}
else if (randomColor > 4 && randomColor <= 7)
{
//_count = 0;
var green:Circle = new Circle(6, 3, 0x009933);
green.x = mouseX;
green.y = mouseY;
_greenCircles["green"+ _count++] = {x:mouseX, y:mouseY, ref:green, radius:green.radius, lastX: Number, lastY:Number,
vel_x:Math.cos(dir_x) * 8.5, vel_y:Math.sin(dir_y) * 8.5 }
green.mouseEnabled = false;
_stage.addChild(green);
}
else if (randomColor > 7)
{
//_count = 0;
var blue:Circle = new Circle(6, 3, 0xff);
blue.x = mouseX;
blue.y = mouseY;
_greenCircles["blue"+ _count++] = {x:mouseX, y:mouseY, ref:blue, radius:blue.radius,lastX: Number, lastY:Number,
vel_x:Math.cos(dir_x) * 8.5, vel_y:Math.sin(dir_y) * 8.5 }
blue.mouseEnabled = false;
_stage.addChild(blue);
}
_count++;
}
}
}
addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
}
private function startEvent(event:Event):void
{
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
moveCircles(_redCircles);
moveCircles(_greenCircles);
moveCircles(_blueCircles);
if (!_springsOn) {
computeParticleMovement(_redCircles);
computeParticleMovement(_greenCircles);
computeParticleMovement(_blueCircles);
}
}
private function onDown(event:MouseEvent):void
{
if (_manualOn)
{
var c:Circle = (event.currentTarget) as Circle;
var rect:Rectangle = new Rectangle(0, 0, WIDTH, HEIGHT-OFFSET_Y - c.radius);
_lastMouseX = c.x;
_lastMouseY = c.y;
c.addEventListener(MouseEvent.MOUSE_UP, onUp);
c.startDrag(false, rect);
event.stopPropagation();
}
}
private function onUp(event:MouseEvent):void
{
stage.removeEventListener(MouseEvent.MOUSE_UP, onUp);
Circle(event.currentTarget).stopDrag();
Circle(event.currentTarget).x = mouseX;
Circle(event.currentTarget).y = mouseY;
Circle(event.currentTarget).removeEventListener(MouseEvent.MOUSE_DOWN, onDown);
}
private function computeParticleMovement(obj:Object):void
{
var p:Particle = _firstParticle;
var color:uint;
if (obj != null)
{
for each (var circle:Object in obj)
{
while (p)
{
p.vel.x = DAMP * p.vel.x;
p.vel.y = DAMP * p.vel.y;
p.acc.x = 0;
p.acc.y = 0;
if (obj == _redCircles)
color = (p.red - REPEL*(p.green + p.blue))
else if (obj == _greenCircles)
color = (p.green - REPEL*(p.red + p.blue))
else if (obj == _blueCircles)
color = (p.blue - REPEL*(p.red + p.green));
_dx = circle.ref.x - p.x;
_dy = circle.ref.y - p.y;
var dist:Number = _dx * _dx + _dy * _dy;
if (dist < _maxDistSQ)
{
var d:Number = Math.pow(dist, 1.6);
if (d < _minDistForce)
d = _minDistForce;
var factor:Number = -color * K*(1/d);
p.acc.x += _dx * factor;
p.acc.y += _dy * factor;
}
if (_sinksOn){
p.acc.x += 0.01*(p.initX - p.x);
p.acc.y += 0.01*(p.initY - p.y);
}
p.vel = p.vel.add(p.acc);
p.x += p.vel.x;
p.y += p.vel.y;
if (p.x < 0) {
p.x = 0;
p.vel.x = -p.vel.x * BOUNCE;
}
if (p.y < 0) {
p.y = 0;
p.vel.y = -p.vel.y * BOUNCE;
}
if (p.x > WIDTH) {
p.x = WIDTH;
p.vel.x = -p.vel.x * BOUNCE;
}
if (p.x > HEIGHT) {
p.x = HEIGHT;
p.vel.y = -p.vel.y * BOUNCE;
}
p = p.next;
}
}
}
_bmpData.lock();
_bmpData.applyFilter(_bmpData, _bmpData.rect, new Point(), _blur);
_bmpData.colorTransform(_bmpData.rect, new ColorTransform(0.98,0.98,0.98, 0.98));
p = _firstParticle;
while (p)
{
_bmpData.setPixel32(p.x, p.y, p.color);
p = p.next;
}
_bmpData.unlock();
}
private function moveCircles(obj:Object):void
{
if (!_manualOn)
{
var friction:Number = 0.99;
for each(var circle:Object in obj)
{
//circle.vel_x *= friction;
//circle.vel_y *= friction;
circle.x += circle.vel_x;
circle.y += circle.vel_y;
if (circle.x + circle.radius > WIDTH)
{
circle.vel_x *= -1;
circle.x -= 1;
} else if (circle.x - circle.radius < 0)
{
circle.vel_x *= -1;
circle.x += 1;
} else if (circle.y + circle.radius > HEIGHT - OFFSET_Y)
{
circle.vel_y *= -1;
circle.y -= 1;
} else if (circle.y - circle.radius < 0)
{
circle.vel_y *= -1;
circle.y += 1;
}
circle.ref.x = circle.x;
circle.ref.y = circle.y;
}
} else if ((_redCircles != null || _greenCircles != null || _blueCircles != null) && _manualOn)
{
for each (circle in obj)
{
Sprite(circle.ref).buttonMode = true;
Sprite(circle.ref).mouseEnabled = true;
Sprite(circle.ref).addEventListener(MouseEvent.MOUSE_DOWN, onDown);
}
}
}
private function onMouseUp(event:MouseEvent):void
{
_mouseDown = !_mouseDown;
}
private function onLoadImage(event:Event):void
{
//event.target.loader.removeEventListener(Event.COMPLETE, onLoadImage);
var pic:Bitmap;
var info:LoaderInfo = event.target as LoaderInfo;
var display:DisplayObject = info.content as DisplayObject;
pic = info.content as Bitmap;
createParticles(pic);
drawFirstFrame();
clearLoader(event.target as LoaderInfo);
_springs.enabled = true;
_sinks.enabled = true;
_manual.enabled = true;
}
private function clearLoader(event:LoaderInfo):void
{
event.removeEventListener(Event.COMPLETE, onLoadImage);
event.removeEventListener(IOErrorEvent.IO_ERROR, IOErrorListener);
}
private function IOErrorListener(event:IOErrorEvent):void
{
try {
throw new Error("Loading failed")
} catch (e:Error)
{
trace(e.toString());
}
clearLoader(event.target as LoaderInfo);
}
private function createParticles(image:Bitmap):void
{
var color:uint;
var numParticles:Number = 0;
var prevParticle:Particle;
var count:Number = 0;
for (var i:int = 0; i <= image.width - 1; i = i+3) //sampling every third pixel on x axis
{
for (var j:int = 0; j <= image.height - 1; j = j+3) // //sampling every third pixel on y axis
{
color = image.bitmapData.getPixel32(i, j);
var pixel:Particle = new Particle(color);
pixel.x = (WIDTH - image.width)/2 + i;
pixel.y = (HEIGHT - image.height - OFFSET_Y)/2 + j;
pixel.initX = pixel.x;
pixel.initY = pixel.y;
pixel.vel.x = 0;
pixel.vel.y = 0;
numParticles++;
if (numParticles == 1)
{
_firstParticle = pixel;
}
else {
pixel.next = _firstParticle;
_firstParticle = pixel;
}
count++;
}
}
_particles.text = count + " particles";
}
private function drawFirstFrame():void
{
var p:Particle = _firstParticle;
do
{
_bmpData.setPixel32(p.x, p.y, p.color);
p = p.next;
} while (p != null)
}
}
}
import flash.geom.Point;
internal class Particle extends Point
{
// Velocity and acceleration values of particles
public var vel:Point = new Point();
public var acc:Point = new Point();
// Initial particle positions
public var initX:Number;
public var initY:Number;
// Color attributes
public var red:uint;
public var green:uint;
public var blue:uint;
public var color:uint;
public var alpha:uint;
public var next:Particle;
public final function Particle(color:uint = 0xffffffff):void
{
this.color = color;
alpha = (color >> 32) & 0xff;
red = (color >> 16) & 0xff;
green = (color >> 08) & 0xff;
blue = (color >> 00) & 0xff;
}
}
import flash.display.Graphics;
import flash.display.Sprite;
import flash.display.Shape;
import flash.geom.Point;
import flash.filters.BlurFilter;
import flash.geom.Matrix;
internal class Circle extends Sprite
{
private var _radius:Number;
private var _color:uint;
private var _thickness:Number;
private var _matrix:Matrix;
public var vx:Number;
public var vy:Number;
public final function Circle(radius:Number = 10, thickness:Number = 4, color:uint = 0x00)
{
this._color = color;
this._radius = radius;
this._thickness = thickness;
_matrix = new Matrix();
_matrix.createGradientBox(2*_radius, 2*_radius, 0);
var circle:Sprite = new Sprite();
circle.cacheAsBitmap = true;
var g:Graphics = circle.graphics;
g.beginGradientFill("radial", [0xCB0000, 0xE28A09], [1, 1], [120, 129], _matrix, "reflect", "rgb", -0.9);
g.lineStyle(_thickness, _color, .8, true, "none");
g.drawCircle(0, 0, _radius);
g.endFill();
circle.filters = [new BlurFilter(4, 4)];
addChild(circle);
}
public function get color():uint
{
return this._color;
}
public function get radius():Number
{
return _radius;
}
}