code on 2009-1-24
package
{
import flash.display.*;
import flash.events.*;
[SWF(width="465", height="465", backgroundColor="0x000000", frameRate="31")]
public class WallPong extends Sprite
{
private var mField:Field;
private var mBall:Ball;
private var mBar:Bar;
function WallPong()
{
mField = new Field(10, 10, 445, 445);
addChild(mField);
mBall = new Ball();
addChild(mBall);
mBall.setPosition(232, 200);
mBall.setVelo(5, -5);
mBar = new Bar();
mBar.setRange(10 + 32, 10 + 445 - 32);
addChild(mBar);
mBar.x = 232;
mBar.y = 420;
mBall.addCollision(mField);
mBall.addCollision(mBar);
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(e:Event):void
{
tick();
updatePosition();
draw();
}
private function tick():void
{
if (mBall.y > 500) {
mBall.setPosition(232, 200);
mBall.setVelo(5, -5);
}
mBar.setPos(mouseX);
mBall.tick();
}
private function updatePosition():void
{
mBall.updatePosition();
}
private function draw():void
{
mBall.draw();
}
}
}
import flash.display.*;
const CIRCLE:int = 1;
class CollisionData
{
public var canceled:Number;
public var sender:ICollisionProvider;
public var N:Vec2;
}
interface ICollisionObject
{
function get shapeType():int;
function get circleRadius():Number;
function get currentPosition():Vec2;
function get currentVelocity():Vec2;
function onCollision(d:CollisionData):void;
}
interface ICollisionProvider
{
function hittest(obj:ICollisionObject):Boolean;
}
class Bar extends Sprite implements ICollisionProvider
{
import flash.geom.*;
public static const GRAD_C:Array = [0xffede0, 0xeed6c2];
public static const GRAD_A:Array = [1, 1];
public static const GRAD_R:Array = [0, 255];
public static const W:Number = 60;
public static const R:Number = 80;
public static const OFS:Number = 75;
public static const ARG:Number = 0.45;
private var mXmin:Number = 0, mXmax:Number = 10;
function Bar():void
{
drawBase();
}
public function setRange(min:Number, max:Number):void
{
mXmin = min;
mXmax = max;
}
public function setPos(px:Number):void
{
if (px < mXmin) px = mXmin;
if (px > mXmax) px = mXmax;
x = px;
}
private function drawBase():void
{
var m:Matrix = new Matrix();
m.createGradientBox(30, 30, 0, -15, -14);
var g:Graphics = graphics;
g.clear();
g.beginGradientFill(GradientType.RADIAL, GRAD_C, GRAD_A, GRAD_R, m);
g.drawCircle(0, OFS, R);
var ms:Sprite = new Sprite();
addChild(ms);
mask = ms;
var mg:Graphics = ms.graphics;
mg.clear();
mg.beginFill(0);
mg.drawRect(-W/2, -5, W, 10);
/*
g.lineStyle(0, 0xff0000);
g.moveTo(0, OFS);
g.lineTo(0 + Math.sin(0.45)*100, OFS - Math.cos(0.45)*100);
*/
}
private var posC:Vec2 = new Vec2();
public function hittest(obj:ICollisionObject):Boolean
{
var pos:Vec2 = obj.currentPosition;
var v:Vec2 = obj.currentVelocity;
if (pos.y > (y+3) || v.y < 0) return false;
posC.x = x;
posC.y = y + OFS;
posC.x = (pos.x + v.x) - posC.x;
posC.y = (pos.y + v.y) - posC.y;
var d:Number = Math2D.norm(posC.x, posC.y);
if ((d-obj.circleRadius) < R)
{
var next_x:Number = pos.x + v.x;
if (next_x > (x -W/2) && next_x < (x +W/2)) {
var ratio:Number = (R-(d-obj.circleRadius)) / Math2D.norm(v.x, v.y);
if (ratio < 0) ratio = -ratio;
var cd:CollisionData = new CollisionData();
cd.canceled = ratio;
cd.sender = this;
cd.N = posC.clone().normalize();
obj.onCollision(cd);
return true;
}
}
return false;
}
}
class Ball extends Sprite implements ICollisionObject
{
import flash.geom.*;
public static const GRAD_C:Array = [0xffffff, 0x77aaff, 0x5588aa];
public static const GRAD_A:Array = [1, 1, 1];
public static const GRAD_R:Array = [0, 90, 255];
public static const R:int = 10;
private var mPos:Vec2 = new Vec2();
private var mVelo:Vec2 = new Vec2(0, 0);
private var mCols:Array = [];
private var mF:Vec2 = new Vec2(0, 0);
function Ball()
{
drawBase();
}
public function addCollision(c:ICollisionProvider):void
{
mCols.push(c);
}
public function setPosition(_x:Number, _y:Number):void
{
mPos.x = _x;
mPos.y = _y;
}
public function addForce(fx:Number, fy:Number):void
{
mF.x = fx;
mF.y = fy;
}
public function setVelo(_x:Number, _y:Number):void
{
mVelo.x = _x;
mVelo.y = _y;
}
private function drawBase():void
{
var m:Matrix = new Matrix();
m.createGradientBox(20, 20, 0, -14, -14);
var g:Graphics = graphics;
g.clear();
g.beginGradientFill(GradientType.RADIAL, GRAD_C, GRAD_A, GRAD_R, m);
g.drawCircle(0, 0, R);
}
public function tick():void
{
mVelo.add(mF);
mF.x = 0;
mF.y = 0;
var c:ICollisionProvider;
for each(c in mCols) {
if (c.hittest(this)) {
}
}
}
public function updatePosition():void
{
mPos.x += mVelo.x;
mPos.y += mVelo.y;
}
public function draw():void
{
x = mPos.x;
y = mPos.y;
}
// collision
public function get shapeType():int
{
return CIRCLE;
}
public function get currentPosition():Vec2
{
return mPos;
}
public function get currentVelocity():Vec2
{
return mVelo;
}
public function onCollision(d:CollisionData):void
{
var iN:Vec2 = new Vec2(-d.N.x, -d.N.y);
var vy:Number = Vec2.dp(iN, mVelo);
var fv:Vec2 = d.N.smul(vy * 2).add(mVelo);
mVelo.smul(1 - d.canceled);
var spd:Number = Math2D.norm(fv.x, fv.y);
if (spd < 18) {
spd += 0.2;
fv.normalize().smul(spd);
}
addForce(fv.x - mVelo.x, fv.y - mVelo.y);
}
public function get circleRadius():Number
{
return R;
}
}
class Field extends Sprite implements ICollisionProvider
{
private var mWidth:int;
private var mHeight:int;
private var mPos:Vec2;
function Field(x1:Number, y1:Number, w:int, h:int)
{
mPos = new Vec2(x1, y1);
mWidth = w;
mHeight = h;
drawBase(x1, y1);
}
private function drawBase(x1:int, y1:int):void
{
var g:Graphics = graphics;
g.clear();
g.beginFill(0x444444);
g.drawRect(x1 - 100, y1, 100, 500);
g.drawRect(x1 + mWidth, y1, 100, 500);
g.drawRect(x1 - 100, y1 - 100, mWidth + 200, 100);
}
public function hittest(obj:ICollisionObject):Boolean
{
if (checkVerticalWall(obj, mPos.x + mWidth, -1))
return true;
if (checkVerticalWall(obj, mPos.x, 1))
return true;
if (checkHorizontalWall(obj, mPos.y, 1))
return true;
return false;
}
private function checkVerticalWall(obj:ICollisionObject, wx:Number, nx:Number):Boolean
{
var pos:Vec2 = obj.currentPosition;
var v:Vec2 = obj.currentVelocity;
if ((v.x * nx) >= 0) return false;
var next_x:Number = pos.x + v.x;
var r:Number = (nx<0) ? obj.circleRadius : -obj.circleRadius;
if ((nx>0 && (next_x+r) < wx) || (nx<0 && (next_x+r) > wx)) {
var ratio:Number = (wx - (next_x+r)) / v.x;
if (ratio < 0) ratio = -ratio;
var cd:CollisionData = new CollisionData();
cd.canceled = ratio;
cd.sender = this;
cd.N = new Vec2(nx, 0);
obj.onCollision(cd);
return true;
}
return false;
}
private function checkHorizontalWall(obj:ICollisionObject, wy:Number, ny:Number):Boolean
{
var pos:Vec2 = obj.currentPosition;
var v:Vec2 = obj.currentVelocity;
if ((v.y * ny) >= 0) return false;
var next_y:Number = pos.y + v.y;
var r:Number = (ny<0) ? obj.circleRadius : -obj.circleRadius;
if ((ny>0 && (next_y+r) < wy) || (ny<0 && (next_y+r) > wy)) {
var ratio:Number = (wy - (next_y+r)) / v.y;
if (ratio < 0) ratio = -ratio;
var cd:CollisionData = new CollisionData();
cd.canceled = ratio;
cd.sender = this;
cd.N = new Vec2(0, ny);
obj.onCollision(cd);
return true;
}
return false;
}
}
// math util
class Vec2
{
function Vec2(aX:Number = 0, aY:Number = 0)
{
x = aX;
y = aY;
}
public var x:Number;
public var y:Number;
public static function dp(v1:Vec2, v2:Vec2):Number
{
return v1.x*v2.x + v1.y*v2.y;
}
public function smul(k:Number):Vec2
{
x *= k;
y *= k;
return this;
}
public function add(v:Vec2):Vec2
{
x += v.x;
y += v.y;
return this;
}
public function normalize():Vec2
{
var nrm:Number = Math2D.norm(x, y);
if (nrm != 0)
{
x /= nrm;
y /= nrm;
}
return this;
}
public function clone():Vec2
{
return new Vec2(x, y);
}
public function left():Vec2
{
return Math2D.leftN(this);
}
public function right():Vec2
{
return Math2D.rightN(this);
}
public function zero():Vec2
{
x = 0;
y = 0;
return this;
}
public function trans(m:M22):Vec2
{
var _x:Number = x;
var _y:Number = y;
x = _x*m._11 + _y*m._12;
y = _x*m._21 + _y*m._22;
return this;
}
}
class Math2D
{
public static function nearZero(n:Number):Boolean
{ return (n > -0.001) && (n < 0.001); }
public static function norm(x:Number, y:Number):Number
{ return Math.sqrt(x*x + y*y); }
public static function leftN(v:Vec2):Vec2
{
var y:Number = v.y;
v.y = -v.x;
v.x = y;
return v;
}
public static function rightN(v:Vec2):Vec2
{
var y:Number = v.y;
v.y = v.x;
v.x = -y;
return v;
}
public static function makeAtoB(a:Vec2, b:Vec2):Vec2
{
return new Vec2(b.x-a.x, b.y-a.y);
}
public static function chkLineCross(A:Vec2,B:Vec2, C:Vec2,D:Vec2, AB_as_ray:Boolean, CD_as_seg:Boolean = true):Vec2
{
var vAB:Vec2 = makeAtoB(A, B);
var vCD:Vec2 = makeAtoB(C, D);
var vCA:Vec2 = makeAtoB(C, A);
var M:M22 = new M22();
M._11 = vCD.x;
M._21 = vCD.y;
M._12 = -vAB.x;
M._22 = -vAB.y;
var Minv:M22 = M.getInvert();
if (Minv == null)
return null;
vCA.trans(Minv);
if ((vCA.y > 1.0 && !AB_as_ray) || vCA.y < 0.0)
return null;
/* else AB is segment */
if (CD_as_seg)
{
if (vCA.x > 1.0 || vCA.x < 0.0)
return null;
}
/* else length is infinity */
return vAB.smul(vCA.y).add(A);
}
}
class M22
{
public var _11:Number = 1;
public var _12:Number = 0;
public var _21:Number = 0;
public var _22:Number = 1;
public function getInvert():M22
{
var out:M22 = new M22();
var det:Number = _11 * _22 - _12 * _21;
if (Math2D.nearZero(det))
return null;
out._11 = _22 / det;
out._22 = _11 / det;
out._12 = -_12 / det;
out._21 = -_21 / det;
return out;
}
}