3d shadow ccd pool
/**
* Copyright lizhi ( http://wonderfl.net/user/lizhi )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/32SO
*/
// forked from lizhi's 3d ccd pool
// forked from makc3d's ff[4]: ccd pool
// forked from makc3d's ff[3]: ccd pool
// forked from makc3d's ff[2]: ccd pool
// forked from lizhi's forked from: ccd pool
// forked from makc3d's ccd pool
// forked from makc3d's Continuous Collision w. Restitution
// forked from generalrelativity's Continuous Elastic Collision
package
{
import flash.display.Bitmap;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageQuality;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.display.BitmapData;
import flash.filters.BlurFilter;
import flash.geom.Matrix;
import flash.geom.Matrix3D;
import flash.geom.PerspectiveProjection;
import flash.geom.Point;
import flash.geom.Utils3D;
import flash.geom.Vector3D;
import flash.utils.ByteArray;
import flash.utils.Dictionary;
import flash.utils.getTimer;
import net.hires.debug.Stats;
import flash.text.TextField;
[SWF(backgroundColor="#DDDDDD", frameRate=60, width=465, height=465)]
public class CCDPool extends Sprite
{
private var simulator:Simulator;
private var p2b:Vector.<BitmapData> = new Vector.<BitmapData>;
private var p2n:Vector.<BitmapData> = new Vector.<BitmapData>;
//3d
private var camera:Matrix3D = new Matrix3D;
private var wrapper:Matrix3D = new Matrix3D;
private var project:Matrix3D = new PerspectiveProjection().toMatrix3D();
private var focalLength:Number = project.rawData[0];
private var force:flash.text.TextField = new flash.text.TextField;
private var shadowMap:BitmapData = new BitmapData(465, 465, true, 0);
private var uvt:Vector.<Number> = Vector.<Number>([10/465, 10/465,0, 455/465, 10/465,0, 10/465, 455/465, 0,455/465, 455/465,0]);
private var vs:Vector.<Number> = Vector.<Number>([ -445 / 2, -445 / 2, 7, 445 / 2, -445 / 2, 7, -445 / 2, 445 / 2, 7, 445 / 2, 445 / 2, 7]);
private var vo:Vector.<Number> = new Vector.<Number>(vs.length);
private var vo3d:Vector.<Number> = new Vector.<Number>();
private var indexs:Vector.<int> = Vector.<int>([0,1,2,1,2,3]);
//3d end
public function CCDPool()
{
addEventListener(Event.ADDED_TO_STAGE, init);
}
public function init(e:Event):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
// colors: http://en.wikipedia.org/wiki/Billiard_ball#International_pool
var ball:Ball = new Ball;
var tr:Number = 50;
p2b[1 -1] = ball.createBallTexture (tr, 255, 255, 0);
p2b[2 -1] = ball.createBallTexture (tr, 0, 0, 255);
p2b[3 -1] = ball.createBallTexture (tr, 255, 0, 0);
p2b[4 -1] = ball.createBallTexture (tr, 255, 0, 255);
p2b[5 -1] = ball.createBallTexture (tr, 255, 127, 0);
p2b[6 -1] = ball.createBallTexture (tr, 0, 255, 0);
p2b[7 -1] = ball.createBallTexture (tr, 127, 63, 0);
p2b[8 -1] = ball.createBallTexture (tr, 0, 0, 0);
shadow = new BitmapData(28, 28, true, 0);
var pen:Sprite = new Sprite;
pen.graphics.beginFill(0);
pen.graphics.drawCircle(14, 14, 10);
pen.graphics.endFill();
pen.filters = [new BlurFilter(4,4,3)];
shadow.draw(pen);
var image:Bitmap = new Bitmap(shadow);
addChild(image);
image.x = 200;
for (var i:int = 1; i < 9; i++) {
p2n[i -1] = ball.createNumberTexture (10, i);
}
initSimulation();
addChild(new Stats);
//3d init
camera.appendTranslation(0, 0, -400);
help2.copyFrom(camera);
help2.invert();
wrapper.appendRotation( -45, Vector3D.X_AXIS);
addChild(force);
force.autoSize = "left";
force.x = 70;
shadowMap.perlinNoise(200, 200, 2, 1, true, true);
stage.quality = StageQuality.MEDIUM;
shadowMap.perlinNoise(15, 15, 7, 1, false, true);
var map:Array = [];
var map2:Array = [];
for (i = 0; i < 0xff;i++ ) {
map[i] = (i/6)<<8;
}
shadowMap.paletteMap(shadowMap, shadowMap.rect, new Point, map, map, map);
shadowByte = shadowMap.getPixels(shadowMap.rect);
//3d init end
}
private function initSimulation() : void
{
simulator = new Simulator();
simulator.addWall( new Wall( 40, 10, 425, 10 ) );
simulator.addWall( new Wall( 455, 40, 455, 425 ) );
simulator.addWall( new Wall( 425, 455, 40, 455 ) );
simulator.addWall( new Wall( 10, 425, 10, 40 ) );
simulator.addParticle( createParticle(232, 230, 0, 0, 1, 10 ) );
for (var i:int = 0; i < 10; i++) {
for (var j:int = 0; j < i; j++) {
var px:Number = 282 + 18 * i, py:Number = 240 + 21 * j - 10 * i;
simulator.addParticle (createParticle (px, py, 0, 0, 1, 10));
simulator.addParticle (createParticle (465 - px, py, 0, 0, 1, 10));
}
}
simulator.addEventListener( Simulator.STEP, onSimulationStep );
simulator.run( this, Event.ENTER_FRAME );
stage.addEventListener (MouseEvent.CLICK, onClick);
}
private function createParticle(xx:Number, xy:Number, vx:Number, vy:Number, mass:Number = 1.0, radius:Number = 5 ):Particle {
var p:Particle = new Particle(xx, xy, vx, vy, mass, radius);
return p;
}
private function onClick(e:MouseEvent):void
{
var p:Particle = simulator.particles[0];
var xy:Vector3D = getMousePos();
if(xy)
p.v=p.v.plus( p.x.minus (new Vec2D (xy.x, xy.y)).times (10*Number(force.text)));
}
private function onSimulationStep( event:Event ) : void
{
render();
for (var i:int = 0, n:int = simulator.particles.length; i < n; i++) {
var p:Particle = simulator.particles[i];
if ((p.x.x < - p.r) || (p.x.y < -p.r) || (p.x.x > 465 + p.r) || (p.x.y > 465 + p.r)) {
simulator.particles.splice (i, 1); i--; n--;
} else {
// linear friction
const f:Number = 0.1;
p.v.normalize (Math.max (0, p.v.magnitude - f));
}
}
p = simulator.particles[0];
graphics.lineStyle (2, 255 * 65536, 0.1);
graphics.moveTo (mouseX, mouseY);
var obj:Object = tp(p.x.x, p.x.y);
graphics.lineTo (obj.x,obj.y);
}
private var matrix:Matrix = new Matrix;
private var matrix2:Matrix = new Matrix;
private var help:Matrix3D=new Matrix3D;
private var help2:Matrix3D=new Matrix3D;
private var shadow:BitmapData;
private var display:Matrix3D=new Matrix3D;
private var v3d:Vector3D = new Vector3D;
private var ir:Number = 1 / 10, ipr:Number = ir / Math.PI, ix:int, iy:int;
private var shadowByte:ByteArray;
private function tp(x:Number, y:Number,z:Number=0):Object {
var obj3d:Object = { };
display.identity();
display.appendTranslation(x - 465 / 2, y - 465 / 2, z);
help.copyFrom(display);
help.append(wrapper);
help.append(help2);
v3d.x = 0;
v3d.y = 0;
v3d.z = 0;
v3d = help.transformVector(v3d);
obj3d.z = v3d.z;
v3d = Utils3D.projectVector(project, v3d);
obj3d.x =v3d.x+ 465 / 2;
obj3d.y = v3d.y + 465 / 2;
return obj3d;
}
private function getMousePos():Vector3D {
var matrix:Matrix3D = new Matrix3D;
matrix.copyFrom(wrapper);
matrix.invert();
matrix.prepend(camera);
var p1:Vector3D = new Vector3D;
p1 = matrix.transformVector(p1);
var p2:Vector3D = new Vector3D(mouseX - 465 / 2, mouseY - 465 / 2,focalLength);
p2 = matrix.transformVector(p2);
p2 = p2.subtract(p1);
var ret:Vector3D = calPlaneLineIntersectPoint(Vector3D.Z_AXIS, new Vector3D(), p2, p1);
ret.x += 465 / 2;
ret.y += 465 / 2;
return ret;
}
/**
* 求平面和直线的交点
* http://blog.csdn.net/abcjennifer/article/details/6688080
* @param planeVector 平面的法线向量
* @param planePoint 平面经过的一点坐标
* @param lineVector 直线的方向向量
* @param linePoint 直线经过的一点坐标
* @return 返回交点坐标
*/
private function calPlaneLineIntersectPoint(planeVector:Vector3D,planePoint:Vector3D,lineVector:Vector3D,linePoint:Vector3D):Vector3D
{
var returnResult:Vector3D = new Vector3D;
var vpt:Number = lineVector.x * planeVector.x + lineVector.y * planeVector.y + lineVector.z * planeVector.z;
//首先判断直线是否与平面平行
if (vpt == 0)
{
returnResult = null;
}
else
{
var t:Number = ((planePoint.x - linePoint.x) * planeVector.x + (planePoint.y - linePoint.y) * planeVector.y + (planePoint.z - linePoint.z) * planeVector.z) / vpt;
returnResult.x = linePoint.x + lineVector.x * t;
returnResult.y = linePoint.y + lineVector.y * t;
returnResult.z = linePoint.z + lineVector.z * t;
}
return returnResult;
}
private function render() : void
{
force.text = "" + (int((getTimer() % 3000) / 100) / 30 as Number).toFixed(1);
wrapper.identity();
wrapper.appendRotation((stage.stageHeight / 2 - mouseY)/10-45, Vector3D.X_AXIS);
wrapper.appendRotation((stage.stageWidth / 2 - mouseX)/10, Vector3D.Y_AXIS);
help.identity();
graphics.clear();
graphics.lineStyle( 1 );
for each( var wall:Wall in simulator.walls )
{
var wallA:Object = tp(wall.A.x, wall.A.y,7);
var wallB:Object = tp(wall.B.x,wall.B.y,7);
var wallA2:Object = tp(wall.A.x, wall.A.y,-7);
var wallB2:Object = tp(wall.B.x,wall.B.y,-7);
graphics.moveTo(wallA.x, wallA.y);
graphics.lineTo( wallA2.x,wallA2.y);
graphics.lineTo( wallB2.x, wallB2.y );
graphics.lineTo( wallB.x, wallB.y );
}
graphics.lineStyle ();
shadowMap.lock();
shadowByte.position = 0;
shadowMap.setPixels(shadowMap.rect, shadowByte);
var particles:Array = [];
for each( var particle:Particle in simulator.particles )
{
var obj3d:Object = tp(particle.x.x, particle.x.y);
obj3d.p = particle;
particles.push(obj3d);
matrix.createBox(1, 1, 0, particle.x.x - particle.r-2, particle.x.y - particle.r-2);
shadowMap.draw(shadow, matrix);
}
shadowMap.unlock();
help.identity();
help.append(wrapper);
help.append(help2);
help.transformVectors(vs,vo);
Utils3D.projectVectors(project, vo, vo3d, uvt);
graphics.lineStyle();
graphics.beginBitmapFill(shadowMap,null,false,true);
for (var i:int = 0; i < vo3d.length;i++ ) {
vo3d[i] += 465 / 2;
}
graphics.drawTriangles(vo3d, indexs, uvt);
graphics.endFill();
particles.sortOn("z", Array.NUMERIC | Array.DESCENDING);
for each(obj3d in particles) {
particle = obj3d.p;
var perspR:Number=particle.r*focalLength/obj3d.z;
var perspR2:Number=perspR*2;
var fill:BitmapData = p2b[particle.number];
matrix.createBox(perspR2/fill.width, perspR2/fill.height, 0, obj3d.x - perspR, obj3d.y - perspR);
graphics.beginBitmapFill (fill, matrix, true, true);
graphics.drawRect (matrix.tx, matrix.ty, perspR2, perspR2);
graphics.endFill ();
//http://wonderfl.net/c/pMzz
// now the numbers are tricky
// real angle depends on ball collision history
// like the guy from 9ria.com, we shall not give a fuck
var rx:Number = Math.cos (ir * particle.x.x);
var ry:Number = Math.cos (ir * particle.x.y);
// this fork attempts to better imitate balls with 2 numbers on them
var r2:Number = rx * rx + ry * ry;
if (r2 < 1) {
ix = ipr * particle.x.x;
iy = ipr * particle.x.y;
} else {
rx = Math.sin (ir * particle.x.x);
ry = Math.sin (ir * particle.x.y);
r2 = rx * rx + ry * ry;
ix = ipr * particle.x.x + 1.5;
iy = ipr * particle.x.y + 1.5;
}
if (ix % 2 == 1) rx *= -1;
if (iy % 2 == 1) ry *= -1;
var nxx:Number, nxy:Number, nyx:Number, nyy:Number;
nxx = 1 - rx * rx; nyx = 0 - rx * ry;
nxy = 0 - rx * ry; nyy = 1 - ry * ry;
// some magic numbers to make it look better, as well as 0.92 below
nxx *= 1.2; nyx *= 1.2;
nxy *= 1.2; nyy *= 1.2;
// this is what I meant:
var scale:Number=focalLength/obj3d.z;
matrix2.a = nxx*scale; matrix2.c = nyx*scale; matrix2.tx = obj3d.x - (0.5*(nxx+nyx)+0.92*rx)*perspR;
matrix2.b = nxy*scale; matrix2.d = nyy*scale; matrix2.ty = obj3d.y - (0.5*(nxy+nyy)+0.92*ry)*perspR;
fill = p2n[particle.number];
graphics.beginBitmapFill (fill, matrix2, false,true);
graphics.drawRect (matrix.tx, matrix.ty, perspR2, perspR2);
graphics.endFill ();
}
/*graphics.lineStyle (0, 0, 0.1);
for each( particle in simulator.particles )
{
var po:Object = tp(particle.x.x, particle.x.y);
graphics.moveTo (po.x, po.y);
po = tp(particle.x.x + particle.v.x, particle.x.y + particle.v.y);
graphics.lineTo (po.x,po.y);
}*/
}
}
}
import flash.display.GradientType;
import flash.display.BitmapData;
import flash.display.SpreadMethod;
import flash.display.Sprite;
import flash.events.EventDispatcher;
import flash.events.Event;
import flash.geom.Matrix;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.utils.getTimer;
//http://bbs.9ria.com/forum.php?mod=redirect&goto=findpost&ptid=161522&pid=1577947&fromuid=15322
class Ball extends Sprite{
private var offset:Number = 0;
public function createBallTexture(ballRadius:Number,r:int=-1,g:int=-1,b:int=-1):BitmapData {
var matr:Matrix = new Matrix();
matr.createGradientBox(ballRadius * 3, ballRadius * 3, 0, -ballRadius * 1.8, -ballRadius * 1.8);
r = (r >= 0) ? r : 0xff * Math.random();
g = (g >= 0) ? g : 0xff * Math.random();
b = (b >= 0) ? b : 0xff * Math.random();
graphics.beginGradientFill(GradientType.RADIAL, [r<<16|g<<8|b,(r/3)<<16|(g/3)<<8|(b/3)], [100, 100], [0x00, 0xFF], matr, SpreadMethod.PAD);
graphics.drawCircle(0,0,ballRadius);
var bd:BitmapData = new BitmapData (Math.ceil(ballRadius)*2, Math.ceil(ballRadius)*2, true, 0);
bd.draw (this, new Matrix (1,0,0,1, Math.ceil(ballRadius), Math.ceil(ballRadius)));
graphics.clear();
return bd;
}
public function createNumberTexture(ballRadius:int,number:int):BitmapData {
var bd:BitmapData = new BitmapData(ballRadius, ballRadius, true, 0xff000000);
graphics.beginFill(0xFFFFFF,.7);
graphics.drawCircle(ballRadius*0.5, ballRadius*0.5, ballRadius*0.4);
bd.draw (this);
var tfd:TextField = new TextField; tfd.autoSize = "left";
var tft:TextFormat = tfd.defaultTextFormat;
tft.size = 7;
tft.font = "simsun";
tfd.defaultTextFormat = tft;
tfd.text = number.toString ();
bd.draw (tfd, new Matrix (1,0,0,1, 0.5*(ballRadius - tfd.width), 0.5*(ballRadius - tfd.height + 1)));
// green -> alpha (green seems to survive "cleartype" better)
bd.copyChannel(bd, bd.rect, bd.rect.topLeft, 2, 8);
graphics.clear();
return bd;
}
}
[Event(name="step", type="Simulator")]
class Simulator extends EventDispatcher
{
public static const STEP:String = "step";
public var particles:Vector.<Particle>;
public var walls:Vector.<Wall>;
//holds all the pairs that passed coarse collision detection
private var coarsePass:Vector.<ICollidablePair>;
private var time:uint;
public function Simulator()
{
super();
particles = new Vector.<Particle>();
walls = new Vector.<Wall>();
}
public function addParticle( particle:Particle ) : void
{
particles.push( particle );
}
public function addWall( wall:Wall ) : void
{
walls.push( wall );
}
//advances the simulation at each dispatch of the passed event type
public function run( updateDispatcher:EventDispatcher, eventType:String = Event.ENTER_FRAME ) : void
{
time = getTimer();
updateDispatcher.addEventListener( eventType, step, false, 0, true );
}
//advances the simulation by the amount of time that has passed since the last step
private function step( event:Event ) : void
{
const MAX_ITERATIONS:uint = 100;
//delta time in milliseconds
var dtms:uint = getTimer() - time;
//delta time in seconds
var elapsed:Number = dtms / 1000;
//start this step at 0 and advance to elapsed
var t:Number = 0;
var dt:Number;
var iteration:uint;
while( t < elapsed && ++iteration <= MAX_ITERATIONS )
{
//start by trying to step over the entire remainder
dt = elapsed - t;
//neglect pairs whose bounding boxes don't overlap
doCoarsePhase( dt );
//holds the next future collision
var minPair:ICollidablePair = null;
var minT:Number = Number.POSITIVE_INFINITY;
for each( var pair:ICollidablePair in coarsePass )
{
//if the collision will happen within the current time-step
//compare the time against the current minimum
if( pair.willCollide( dt ) )
{
//if it's less, store it as the min and proceed
if( pair.timeToCollision < minT )
{
minT = pair.timeToCollision;
minPair = pair;
}
}
}
//change the actual time to integrate
if( minT < Number.POSITIVE_INFINITY ) dt = minT;
//update the simulation to the time of collision
for each( var particle:Particle in particles )
{
particle.integrate( dt - 1e-8 );
}
//resolve the collision instantaneously
if( minPair != null )
{
minPair.resolve();
}
//update time by the stepped amount
t += dt;
}
time += dtms;
dispatchEvent( new Event( Simulator.STEP ) );
}
//rules out some unnecessary collision checks
private function doCoarsePhase( dt:Number ) : void
{
coarsePass = new Vector.<ICollidablePair>();
var aabb:AABB;
for each( var particle:Particle in particles )
{
//update the particle's bounding box to account for its velocity
particle.update( dt );
aabb = particle.aabb;
//check each particle against each wall
for each( var wall:Wall in walls )
{
if( aabb.isOverlapping( wall.aabb ) )
{
coarsePass.push( new ParticleWallPair( particle, wall ) );
}
}
}
var n:int = particles.length;
//check each particle against each other
for( var i:int = 0; i < n - 1; i++ )
{
var p1:Particle = particles[ i ];
aabb = p1.aabb;
for( var j:int = i + 1; j < n; j++ )
{
var p2:Particle = particles[ j ];
if( aabb.isOverlapping( p2.aabb ) )
{
coarsePass.push( new ParticleParticlePair( p1, p2 ) );
}
}
}
}
}
//describes a common interface for collision pairs
interface ICollidablePair
{
function get timeToCollision() : Number;
function willCollide( dt:Number ) : Boolean;
function resolve() : void;
}
class ParticleParticlePair implements ICollidablePair
{
public var p1:Particle;
public var p2:Particle;
private var t:Number;
public function ParticleParticlePair( p1:Particle, p2:Particle )
{
this.p1 = p1;
this.p2 = p2;
}
public function get timeToCollision() : Number
{
return t;
}
public function willCollide( dt:Number ) : Boolean
{
const EPSILON:Number = 1e-4;
//points from 1 -> 2
var dx:Vec2D = p2.x.minus( p1.x );
//if the circle's are already overlapped, return true (this brings the sim to a halt)
var c:Number = dx.dot( dx ) - ( p1.r + p2.r ) * ( p1.r + p2.r );
if( c < 0 )
{
t = EPSILON;
return true;
}
//relative velocity
var dv:Vec2D = p2.v.minus( p1.v );
var a:Number = dv.dot( dv );
if( a < EPSILON ) return false; //not moving enough toward each other to warrant a response
var b:Number = dv.dot( dx );
if( b >= 0 ) return false; //moving apart
var d:Number = b * b - a * c;
if( d < 0 ) return false; //no intersection
t = ( -b - Math.sqrt( d ) ) / a;
//circle's collide if the time of collision is within the current time-step
return t <= dt;
}
//simulation has been updated so that the particles are just colliding
public function resolve() : void
{
//points from 1 -> 2
var cn:Vec2D = p2.x.minus( p1.x );
cn.normalize();
//relative velocity
var dv:Vec2D = p2.v.minus( p1.v );
//perfectly elastic impulse
var impulse:Number = cn.dot( dv.times( -2 ) ) / cn.dot( cn.times( 1 / p1.mass + 1 / p2.mass ) );
//scale normal by the impulse
p1.v.plusEquals( cn.times( -impulse / p1.mass ) );
p2.v.plusEquals( cn.times( impulse / p2.mass ) );
//damping
p1.v.x *= p1.restitution;
p1.v.y *= p1.restitution;
p2.v.x *= p2.restitution;
p2.v.y *= p2.restitution;
}
}
class ParticleWallPair implements ICollidablePair
{
public var p:Particle;
public var w:Wall;
private var t:Number;
public function ParticleWallPair( p:Particle, w:Wall )
{
this.p = p;
this.w = w;
}
public function get timeToCollision() : Number
{
return t;
}
public function willCollide( dt:Number ) : Boolean
{
//this is line/line intersection
//A is the position of the particle
//B is the position + velocity
//together they make the segment AB
//CD is the line segment made up of the wall's end points
var A:Vec2D = p.x;
var B:Vec2D = p.x.plus( p.v );
var AB:Vec2D = B.minus( A );
//inflate the normal by the particle's radius
var normScaledRadius:Vec2D = w.normal.times( -p.r );
//push the wall segment in by this amount
var C:Vec2D = w.A.plus( normScaledRadius );
var D:Vec2D = w.B.plus( normScaledRadius );
var CD:Vec2D = D.minus( C )
var AC:Vec2D = C.minus( A );
t = w.normal.dot( AC ) / w.normal.dot( AB );
if( isNaN( t ) ) t = 0;
var couldCollide:Boolean = (t <= dt) && (t >= 0);
// the above is very "approximate" collision test
// let's try to refine it checking particle position
// at the time of supposed collision
if (couldCollide) {
// A <- future particle position
A = p.x.plus (p.v.times (t));
// see if it projects into the wall along its normal
AC = A.minus (C);
// CD should have been called DC :)
var m:Number = CD.magnitude;
var dot:Number = AC.dot (CD.times (1 / m));
if((dot < 0) || (dot > m)) {
// collision is unlikely
return false;
}
}
return couldCollide;
}
//simulation has been updated so that the particles are coincident
public function resolve() : void
{
var cn:Vec2D = w.normal;
//relative velocity
var dv:Vec2D = p.v;
//perfectly elastic
var impulse:Number = cn.dot( dv.times( -2 ) ) / ( 1 / p.mass );
p.v.plusEquals( cn.times( impulse / p.mass ) );
//damping
p.v.x *= p.restitution;
p.v.y *= p.restitution;
}
}
class Wall
{
public var A:Vec2D;
public var B:Vec2D;
public var aabb:AABB;
public var normal:Vec2D;
public function Wall( ax:Number, ay:Number, bx:Number, by:Number )
{
A = new Vec2D( ax, ay );
B = new Vec2D( bx, by );
normal = new Vec2D( B.y - A.y, -( B.x - A.x ) );
normal.normalize();
aabb = new AABB();
aabb.minx = Math.min( ax, bx );
aabb.maxx = Math.max( ax, bx );
aabb.miny = Math.min( ay, by );
aabb.maxy = Math.max( ay, by );
}
}
class Particle
{
public var number:uint;
public var restitution:Number = 0.7;
//position
public var x:Vec2D;
//velocity
public var v:Vec2D;
public var mass:Number;
//radius
public var r:Number;
//bounding box
public var aabb:AABB;
public function Particle( xx:Number, xy:Number, vx:Number, vy:Number, mass:Number = 1.0, radius:Number = 5 )
{
number = Math.random () * 8; number %= 8; // solids + 8th ball
x = new Vec2D( xx, xy );
v = new Vec2D( vx, vy );
this.mass = mass;
this.r = radius;
aabb = new AABB();
}
public function update( t:Number ) : void
{
var xt:Number = x.x + v.x * t;
var yt:Number = x.y + v.y * t;
var minx:Number = Math.min( x.x, xt );
var maxx:Number = Math.max( x.x, xt );
var miny:Number = Math.min( x.y, yt );
var maxy:Number = Math.max( x.y, yt );
aabb.minx = minx - r;
aabb.maxx = maxx + r;
aabb.miny = miny - r;
aabb.maxy = maxy + r;
}
public function integrate( dt:Number ) : void
{
x.x += v.x * dt;
x.y += v.y * dt;
}
}
class AABB
{
public var minx:Number = 0;
public var maxx:Number = 0;
public var miny:Number = 0;
public var maxy:Number = 0;
public function isOverlapping( aabb:AABB ) : Boolean
{
if( minx > aabb.maxx ) return false;
if( miny > aabb.maxy ) return false;
if( maxx < aabb.minx ) return false;
if( maxy < aabb.miny ) return false;
return true;
}
}
class Vec2D
{
public var x:Number;
public var y:Number;
public function Vec2D( x:Number = 0.0, y:Number = 0.0 )
{
this.x = x;
this.y = y;
}
public function plusEquals( vec2D:Vec2D ) : void
{
x += vec2D.x;
y += vec2D.y;
}
public function plus( vec2D:Vec2D ) : Vec2D
{
return new Vec2D( x + vec2D.x, y + vec2D.y );
}
public function minus( vec2D:Vec2D ) : Vec2D
{
return new Vec2D( x - vec2D.x, y - vec2D.y );
}
public function times( s:Number ) : Vec2D
{
return new Vec2D( x * s, y * s );
}
public function dot( vec2D:Vec2D ) : Number
{
return x * vec2D.x + y * vec2D.y;
}
public function get magnitude() : Number
{
return Math.sqrt( x * x + y * y );
}
public function normalize(v : Number = 1) : void
{
var length:Number = magnitude;
if( length == 0 ) return;
x *= v / length;
y *= v / length;
}
}