Very simple ball simulation
I learned from here:http://82.30.70.163/BlueThen/wordpress/index.php/processing-app/do-you-like-balls/
/**
* Copyright heroboy ( http://wonderfl.net/user/heroboy )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/eKyu
*/
package
{
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.events.Event;
import flash.geom.Point;
import flash.geom.Rectangle;
import net.hires.debug.Stats;
/**
* ...
* @author ywq
*/
public class Main extends Sprite
{
public var balls:Vector.<Ball> = new Vector.<Ball>();
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
public function createBalls():void
{
var bounds:Rectangle = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);
var x:int,y:int;
for (y = 0; y < 10;++y )
{
for (x = 0; x < 20;++x )
{
balls.push(new Ball(new Point( x*22+20+Math.random()*2,y*22+20 +Math.random()*2), bounds));
}
}
}
public var pause:Boolean = false;
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
stage.align = StageAlign.TOP_LEFT;
createBalls();
addEventListener(Event.ENTER_FRAME, onEnterFrame);
stage.addEventListener("click", function(e:Event):void
{
if (pause)
{
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
else
{
removeEventListener(Event.ENTER_FRAME, onEnterFrame);
}
pause = !pause;
});
addChild(new Stats());
}
private function update(dt:Number):void
{
const g:Point = new Point(0, 9.0);
var b:Ball;
//add gravity
for each(b in balls)
{
b.applyForce(g);
}
for each(b in balls)
{
b.update(dt);
}
//make collide
var grids:Vector.<Vector.<Ball>>;// = new Vector.<Vector.<Ball>>();
var grid_size:Number = 22;
var cols:int = int(stage.stageWidth / grid_size) + 1;
var rows:int = int(stage.stageHeight / grid_size) + 1;
var idx:int;
grids = new Vector.<Vector.<Ball>>(cols * rows, true);
for each(b in balls)
{
var ix:int = int(Math.ceil(b.position.x / grid_size));
var iy:int = int(Math.ceil(b.position.y / grid_size));
if (ix >= 0 && ix < cols &&
iy >= 0 && iy < rows)
{
idx = iy * cols + ix;
if (grids[idx] == null)
grids[idx] = new Vector.<Ball>();
grids[idx].push(b);
}
else
{
trace("out of bound?");
}
}
var y:int, x:int;
var drawLine:Boolean = true;
graphics.lineStyle(1.0, 0x888888, 0.8);
graphics.beginFill(0xff0000, 1);
graphics.drawCircle(5, 5, 5);
graphics.endFill();
for ( x = 0; x < stage.stageWidth && drawLine; x += grid_size )
{
graphics.moveTo(x, 0);
graphics.lineTo(x, stage.stageHeight);
}
for (y = 0; y < stage.stageHeight && drawLine; y += grid_size )
{
graphics.moveTo(0, y);
graphics.lineTo(stage.stageWidth, y);
}
for (y = 0; y < rows;++y )
for (x = 0; x < cols;++x )
{
idx = y * cols + x;
var currentGrid:Vector.<Ball> = grids[idx];
if (currentGrid == null) continue;
var i:int, j:int;
for (i = 0; i < currentGrid.length;++i )
for (j = i + 1; j < currentGrid.length;++j)
{
Ball.checkCollide(currentGrid[i], currentGrid[j]);
if (drawLine) Ball.drawCollideLine(currentGrid[i], currentGrid[j], graphics);
}
var idx_right:int = y * cols + x + 1;
var idx_bottom:int = (y + 1) * cols + x;
var idx_rb:int = (y + 1) * cols + x + 1;
var idx_ru:int = (y - 1) * cols + x + 1;
if (x + 1 < cols && grids[idx_right] != null)
{
for (i = 0; i < currentGrid.length;++i )
for (j = 0; j < grids[idx_right].length;++j)
{
Ball.checkCollide(currentGrid[i], grids[idx_right][j]);
if (drawLine) Ball.drawCollideLine(currentGrid[i], grids[idx_right][j], graphics);
}
}
if (y + 1 < rows && grids[idx_bottom] != null)
{
for (i = 0; i < currentGrid.length;++i )
for (j = 0; j < grids[idx_bottom].length;++j)
{
Ball.checkCollide(currentGrid[i], grids[idx_bottom][j]);
if (drawLine) Ball.drawCollideLine(currentGrid[i], grids[idx_bottom][j], graphics);
}
}
if (x + 1 < cols && y + 1 < rows && grids[idx_rb] != null)
{
for (i = 0; i < currentGrid.length;++i )
for (j = 0; j < grids[idx_rb].length;++j)
{
Ball.checkCollide(currentGrid[i], grids[idx_rb][j]);
if (drawLine) Ball.drawCollideLine(currentGrid[i], grids[idx_rb][j], graphics);
}
}
if (x + 1<cols&& y-1>=0 && grids[idx_ru]!=null)
{
for (i = 0; i < currentGrid.length;++i )
for (j = 0; j < grids[idx_ru].length;++j)
{
Ball.checkCollide(currentGrid[i], grids[idx_ru][j]);
if (drawLine) Ball.drawCollideLine(currentGrid[i], grids[idx_ru][j], graphics);
}
}
}
for each(b in balls)
{
b.checkBound(b.position,true);
}
}
private function onEnterFrame(e:Event):void
{
var dt:Number = 0.1;
graphics.clear();
update(dt);
var b:Ball;
for each(b in balls)
{
b.draw(graphics);
}
}
}
}
import flash.display.Graphics;
import flash.geom.Point;
import flash.geom.Rectangle;
/**
* ...
* @author ywq
*/
class Ball
{
public static const restitution:Number = 1;
public var mass:Number = 1.0;
public var imass:Number = 1 / mass;
public var position:Point = new Point();
public var lastPosition:Point = new Point();
public var force:Point = new Point();
public var radius:Number = 10;
public var v:Point = new Point();
public var bounds:Rectangle;
public var lastdt:Number = 0.01;
public function Ball(pos:Point,bounds:Rectangle)
{
this.position = pos.clone();
this.lastPosition = pos.clone();
this.bounds = bounds;
}
public function applyForce(f:Point):void
{
//force.offset(f.x, f.y);
force = force.add(f);
}
public function update(dt:Number):void
{
lastdt = dt;
var newPosition:Point = position.clone();
var acc:Point = force.clone();
acc.x *= imass;
acc.y *= imass;
newPosition.x += (position.x - lastPosition.x) + 0.5 * acc.x * dt * dt;
newPosition.y += (position.y - lastPosition.y) + 0.5 * acc.y * dt * dt;
//
lastPosition = position;
position = newPosition;
v.x = (position.x - lastPosition.x)/dt;
v.y = (position.y - lastPosition.y)/dt;
//
force.x = force.y = 0;
//bound(position, true);
}
public function checkBound(position:Point,needApplyForce:Boolean):void
{
var force:Point = new Point;
if (position.x < bounds.left + radius)
{
force.x = bounds.left + radius - position.x;
position.x = bounds.left + radius;
}
else if (position.x > bounds.right - radius)
{
force.x = bounds.right - radius - position.x;
position.x = bounds.right - radius;
}
if (position.y < bounds.top + radius)
{
force.y = bounds.top + radius - position.y;
position.y = bounds.top + radius;
}
else if (position.y > bounds.bottom - radius)
{
force.y = bounds.bottom - radius - position.y;
position.y = bounds.bottom - radius;
}
if (needApplyForce)
{
var forceNormal:Point = force.clone();
forceNormal.normalize(1.0);
var vn:Number = v.x * forceNormal.x + v.y * forceNormal.y;
if (vn >= 0) return;
var i:Number = -(1 + restitution) * vn / (imass);
force.normalize(1.0);
force.x *= i;
force.y *= i;
applyForce(force);
}
}
public function draw(g:Graphics):void
{
g.lineStyle(1.0, 0xff0000);
g.drawCircle(position.x, position.y, radius);
}
public function drawForce(g:Graphics):void
{
g.lineStyle(1.0, 0x00ff00);
g.moveTo(position.x, position.y);
g.lineTo(position.x + force.x, position.y + force.y);
}
public static function drawCollideLine(b1:Ball, b2:Ball,g:Graphics):void
{
g.lineStyle(1.0, 0x00ff00, 0.8);
g.moveTo(b1.position.x, b1.position.y);
g.lineTo(b2.position.x, b2.position.y);
}
public static function checkCollide(b1:Ball, b2:Ball):void
{
if (b1 === b2) return;
var delta:Point = b1.position.subtract(b2.position);
var dist2:Number = delta.x * delta.x + delta.y * delta.y;
var rr:Number = b1.radius + b2.radius;
if (dist2 >= rr * rr)
return;
var dist:Number = Math.sqrt(dist2);
if (dist == 0) dist = rr - 1;
var mtd:Point = delta.clone();
var _mm:Number = (rr - dist) / dist;
mtd.x *= _mm;
mtd.y *= _mm;
//move
var im1:Number = b1.imass / (b1.imass + b2.imass);
var im2:Number = b2.imass / (b1.imass + b2.imass);
b1.position.x += im1 * mtd.x;
b1.position.y += im1 * mtd.y;
b2.position.x -= im2 * mtd.x;
b2.position.y -= im2 * mtd.y;
//fix velocity
var v:Point = b1.v.subtract(b2.v);
var mtdNormal:Point = mtd.clone();
mtdNormal.normalize(1.0);
var vn:Number = v.x * mtdNormal.x + v.y * mtdNormal.y;
if (vn > 0) return;
var i:Number = -(1 + restitution) * vn / (b1.imass + b2.imass);
var impulse:Point = mtdNormal.clone();
impulse.x *= i;
impulse.y *= i;
b1.applyForce(impulse);
impulse.x = -impulse.x;
impulse.y = -impulse.y;
b2.applyForce(impulse);
}
}