Blob simulation with Verlet integration
[arrow key or mouse drag] move the blob
[d] toggle debug output
todo
* modify the blob not to slip
* modify the blob to rotate more naturally
and more...
/**
* Copyright jerryrom ( http://wonderfl.net/user/jerryrom )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/rwRa
*/
// [arrow key or mouse drag] move the blob
// [d] toggle debug output
// todo
// * modify the blob not to slip
// * modify the blob to rotate more naturally
// and more...
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.KeyboardEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.ui.Keyboard;
import net.hires.debug.Stats;
/**
* ...
* @author Masahiro Hayashi
*/
[SWF(width=465, height=465, backgroundColor=0xA9D0F5, frameRate=50)]
public class Main extends Sprite {
private const N:uint = 50;
private var points:Vector.<VerletPoint>;
private var sticks:Vector.<VerletStick>;
private var cw:Number;
private var ch:Number;
private var stageRect:Rectangle;
private var gravity:Number;
private var guide:Sprite;
private var stats:Stats;
private var debug:Boolean = false;
public function Main():void {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void {
addChild(new Stats());
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
graphics.beginFill(0xA9D0F5);
graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight);
graphics.endFill();
guide = new Sprite();
addChild(guide);
stageRect = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);
cw = stage.stageWidth / 2;
ch = stage.stageHeight / 2;
points = new Vector.<VerletPoint>();
sticks = new Vector.<VerletStick>();
gravity = 0.1;
points.push(new VerletPoint(cw, ch));
var i:uint = 0;
var r:Number = 2 * Math.PI / N;
for (i = 1; i <= N; i++) {
points.push(new VerletPoint(cw + Math.cos(r * i) * 50, ch + Math.sin(r * i) * 50));
sticks.push(new VerletStick(points[0], points[i], 0.2));
}
for (i = 1; i <= N; i++) {
if (i == N) {
sticks.push(new VerletStick(points[i], points[1], 0.02))
} else {
sticks.push(new VerletStick(points[i], points[i + 1], 0.02));
}
}
/*
for (i = 1; i <= N; i++) {
if (i == N - 1) {
sticks.push(new VerletStick(points[i], points[1]))
} else if (i == N) {
sticks.push(new VerletStick(points[i], points[2]))
} else {
sticks.push(new VerletStick(points[i], points[i + 2]));
}
}*/
stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
this.addEventListener(Event.ENTER_FRAME, onFrame);
}
private function onMouseUp(e:MouseEvent):void {
guide.graphics.clear();
}
private function onMouseDown(e:MouseEvent):void {
if (debug) {
guide.graphics.clear();
guide.graphics.lineStyle(1, 0xff0000, 1);
guide.graphics.moveTo(points[0].x, points[0].y);
guide.graphics.lineTo(mouseX, mouseY);
}
}
private function onMouseMove(e:MouseEvent):void {
if (e.buttonDown) {
points[0].x += (mouseX - points[0].x) * 0.1;
points[0].y += (mouseY - points[0].y) * 0.1;
if (debug) {
guide.graphics.clear();
guide.graphics.lineStyle(1, 0xff0000, 1);
guide.graphics.moveTo(points[0].x, points[0].y);
guide.graphics.lineTo(mouseX, mouseY);
}
} else {
guide.graphics.clear();
}
}
private function onKeyDown(e:KeyboardEvent):void {
var n:Number = N;
while(n--) {
switch (e.keyCode) {
case Keyboard.LEFT: points[n].x -= 0.3; break;
case Keyboard.RIGHT: points[n].x += 0.3; break;
case Keyboard.UP: points[n].y -= 0.7; break;
case Keyboard.DOWN: points[n].y += 0.3; break;
default: break;
}
}
if (e.keyCode == 68) debug = !debug;
}
private function onFrame(e:Event):void {
var i:uint;
for (i = 0; i < N + 1; i++) {
points[i].y += gravity;
points[i].update();
points[i].constrain(stageRect);
}
for (i = 0; i < N * 2; i++) {
sticks[i].update();
}
for (i = 0; i < N * 2; i++) {
sticks[i].update();
}/*
for (i = 0; i < N * 2; i++) {
sticks[i].update();
}*/
//render
graphics.clear();
if (debug) { graphics.lineStyle(1, 0x000000, 1); graphics.beginFill(0xffffff); }
else { graphics.beginFill(0xffffff);}
for (i = 0; i < N; i++) {
graphics.moveTo(sticks[i].pointA.x, sticks[i].pointA.y);
graphics.lineTo(sticks[i + N].pointB.x, sticks[i + N].pointB.y);
if (i == N-1) {
graphics.lineTo(sticks[1].pointB.x, sticks[1].pointB.y);
} else {
graphics.lineTo(sticks[i+N+1].pointB.x, sticks[i+N+1].pointB.y);
}
graphics.lineTo(points[0].x, points[0].y);
}
if (debug) graphics.lineStyle(1, 0xff0000, 1);
graphics.moveTo(points[0].x, points[0].y);
graphics.lineTo(points[1].x, points[1].y);
graphics.endFill();
}
}
}
internal class VerletStick{
public var pointA:VerletPoint;
public var pointB:VerletPoint;
private var length:Number = -1;
private var min:Number;
private var max:Number;
private var softness:Number;
public function VerletStick(pointA:VerletPoint, pointB:VerletPoint, softness:Number = -1) {
this.pointA = pointA;
this.pointB = pointB;
this.softness = softness;
var dx:Number = pointA.x - pointB.x;
var dy:Number = pointA.y - pointB.y;
length = Math.sqrt(dx * dx + dy * dy);
}
public function update():void {
var dx:Number = 0;
var dy:Number = 0;
var dest:Number = 0;
var diff:Number = 0;
var offsetX:Number = 0;
var offsetY:Number = 0;
var n:int = 10;
while (n--) {
dx = pointB.x - pointA.x;
dy = pointB.y - pointA.y;
dest = Math.sqrt(dx * dx + dy * dy);
diff = length - dest;
if (softness != -1) {
min = length * (1 - softness);
max = length * (1 + softness);
if (dest < min) {
diff = min - dest;
} else if (max < dest) {
diff = max - dest;
} else {
diff = (length - dest) * 0.05;
}
offsetX = (diff * dx / dest) / 16;
offsetY = (diff * dy / dest) / 16;
} else {
dx = pointB.x - pointA.x;
dy = pointB.y - pointA.y;
dest = Math.sqrt(dx * dx + dy * dy);
diff = length - dest;
offsetX = (diff * dx / dest) / 2;
offsetY = (diff * dy / dest) / 2;
}
//if (pointA.y - offy >= 465 || pointB.y + offy >= 465) {
//offx *= 0.1;
//}
pointA.x -= offsetX;
pointA.y -= offsetY;
pointB.x += offsetX;
pointB.y += offsetY;
}
}
}
import flash.geom.Rectangle;
/**
* ...
* @author Masahiro Hayashi
*/
internal class VerletPoint{
public var x:Number;
public var y:Number;
private var oldx:Number;
private var oldy:Number;
public function VerletPoint(x:Number, y:Number) {
setPos(x,y);
}
public function setPos(x:Number, y:Number):void{
this.x = oldx = x;
this.y = oldy = y;
}
public function update():void {
var tempx:Number = x;
var tempy:Number = y;
x += x - oldx;
y += y - oldy;
oldx = tempx;
oldy = tempy;
}
public function constrain(rect:Rectangle):void {
x = Math.max(rect.left, Math.min(rect.right, x));
y = Math.max(rect.top, Math.min(rect.bottom, y));
}
public function copy ():VerletPoint {
return new VerletPoint(x, y);
}
}