Ballistics 4: collisions
http://johnblackburne.blogspot.com/2012/02/ball-wall-collisions.html
// forked from John_Blackburne's Ballistics 3
// forked from John_Blackburne's Trig-free ballistics 2
// forked from John_Blackburne's Trig-free ballistics
// forked from John_Blackburne's New ballistic demo
package
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Matrix;
import flash.events.MouseEvent;
import flash.text.TextField;
[SWF(backgroundColor="#000000", frameRate="30")]
public class Main extends Sprite {
// gravity: positive as y measured downwards.
// Adjusted for the frame rate
private var fGravity:Number = 10 / stage.frameRate;
// dimensions of all game objects
private const fSize:Number = 8.0;
// gun position, target initial position
private const fGunX:Number = 250.0;
private const fGunY:Number = 350.0;
private const fInitX:Number =400.0;
private const fInitY:Number =400.0;
// speed and speed squared
private const fSpeed:Number= 10.0;
private const fSpeed2:Number= fSpeed * fSpeed;
// the angle of rotation per frame
private var stepAngle:Number;
// game objects
private var gun:Bitmap;
private var ball:Shape, target:Shape;
private var txt:TextField;
private var fBallX:Number, fBallY:Number, fBallU:Number, fBallV:Number;
// target position
private var tx:Number, ty:Number;
// the following variable pairs represent complex numbers
// (r1, i1), the current rotation/aim direction
private var r1:Number, i1:Number;
// (r2, i2), the target aim direction
private var r2:Number, i2:Number;
// (rDelta, iDelta), fixed speed rotation per frame
private var rDelta:Number, iDelta:Number;
// (rStep, iStep), rotation per frame + direction
private var rStep:Number, iStep:Number;
// last and target angle, used to work out the rotation time
private var fAngle1:Number, fAngle2:Number;
// times for rotating, shooting and the current time
private var iTime1:int, iTime2:int, iTime:int;
// initialise variables, start events
public function Main():void {
MakeThings();
r1 = r2 = 0;
i1 = i2 = -1;
fAngle1 = fAngle2 = 0;
RotateGun(r1, -i2);
init();
// default speed is thirty degrees/second, or 1 degree/frame
stepAngle = Math.PI / 6 / 30;
rDelta = Math.cos(stepAngle);
iDelta = Math.sin(stepAngle);
addEventListener(Event.ENTER_FRAME, update);
stage.addEventListener(MouseEvent.MOUSE_DOWN, click);
}
// handle mouse click
private function click(e:MouseEvent):void {
init(e.stageX, e.stageY);
}
// create all the graphics objects
private function MakeThings():void {
var data:BitmapData;
txt = new TextField();
txt.width = txt.height = 465;
txt.textColor = 0xffffff;
addChild(txt);
ball = new Shape();
ball.graphics.beginFill(0xafffff);
ball.graphics.drawCircle(0, 0, fSize);
ball.graphics.endFill();
addChild(ball);
target = new Shape();
target.graphics.lineStyle(1, 0xff8000);
target.graphics.drawCircle(0, 0, fSize);
addChild(target);
data = new BitmapData(fSize * 10, fSize);
data.fillRect(data.rect, 0xffafffff);
gun = new Bitmap(data);
addChild(gun);
}
// draw curve(s)
private function DrawCoverage():void {
// draw the border of the region shots can reach.
// Note that the upper missile path always touches this
var fMidY:Number, fLeftY:Number;
fMidY = fGunY - 0.5 * fSpeed2 / fGravity;
fLeftY = fMidY + 0.5 * fGravity * fGunX * fGunX / fSpeed2;
graphics.beginFill(0x202020);
graphics.lineStyle(2, 0x808080);
graphics.moveTo(0, fLeftY);
graphics.curveTo(fGunX, fMidY + fMidY - fLeftY, stage.stageWidth, fLeftY);
graphics.lineTo(stage.stageWidth, stage.stageHeight);
graphics.lineTo(0, stage.stageHeight);
graphics.lineTo(0, fLeftY);
}
// update the gun's rotation
private function RotateGun(r2:Number, i2:Number):void {
var r:Number = -r2, i:Number = -i2;
gun.transform.matrix = new Matrix(r, i, -i, r,
fGunX + i * fSize / 2, fGunY - r * fSize / 2);
}
// set up a new target
private function init(_x:Number = fInitX, _y:Number = fInitY):void {
var fDist:Number, fHeight:Number, fGravity2:Number,
fDisc:Number, fB:Number, fTime:Number;
// clear background and redraw
graphics.clear();
graphics.beginFill(0);
graphics.drawRect(0, 0, 500, 500);
// initialise target and missile positions
target.x = tx = _x;
target.y = ty = _y;
ball.x = fGunX;
ball.y = fGunY;
// draw gun 'base'
graphics.beginFill(0x404040, 1);
graphics.drawCircle(fGunX, fGunY, fSize);
// work out the velocity
// get the range
fDist = tx - fGunX;
fHeight = ty - fGunY;
// get the discriminant of the quadratic equation
fGravity2 = fGravity * fGravity;
fB = fSpeed2 + fHeight * fGravity;
fDisc = fB * fB - fGravity2 * (fDist * fDist + fHeight * fHeight);
txt.text = "";
// if the discriminant is negative then
// cannot solve, which means target is outside of coverage area
if (fDisc < 0) {
txt.appendText("Too far/too high for launch speed");
// set the time to something big, to stop updates
iTime = 1000;
DrawCoverage();
return;
}
DrawCoverage();
graphics.lineStyle();
// get current aim direction
fAngle1 = Math.atan2(r1, -i1);
// finish calculation
fDisc = Math.sqrt(fDisc);
// use longer time, higher/looping shot
// (change + to - to shoot at shallow angle: see previous demo)
fTime = Math.sqrt(2 * (fB + fDisc) / fGravity2);
r2 = fDist / fTime / fSpeed;
i2 = -Math.sqrt(1 - r2 * r2);
fAngle2 = Math.atan2(r2, -i2);
txt.appendText ("angle from: " + String(180 / Math.PI * fAngle1));
txt.appendText ("\nangle to: " + String(180 / Math.PI * fAngle2));
AimGun();
// set times: time starts negative and counts up to zero
// while gun is rotating, then is positive while missile is
// on its way
iTime = -iTime1;
// iTime2 = fTime;
iTime2 = (stage.stageWidth + 40) / (2 * Math.abs(r2) * fSpeed);
}
// set up gun aiming
private function AimGun():void {
var fAngle:Number = fAngle2 - fAngle1;
// fixed speed
// it is either the same as initially calculated or
// its conjugate, based on the rotation direction
if (fAngle2 > fAngle1) {
rStep = rDelta;
iStep = iDelta;
} else {
rStep = rDelta;
iStep = -iDelta;
}
// get the time from the angles
iTime1 = Math.abs(fAngle) / stepAngle;
// rotate gun to current direction
RotateGun(r1, i1);
}
// update aim direction
private function UpdateAim():void {
var fR:Number, fI:Number;
// just multiply the complex numbers, using temporary
// variables as the answer is written back into (r1, i1)
fR = r1; fI = i1;
r1 = fR * rStep - fI * iStep;
i1 = fI * rStep + fR * iStep;
// update gun orientation
RotateGun(r1, i1);
}
// update missile location
private function UpdatePos():void {
var fMax:Number = stage.stageHeight;
fBallV += fGravity;
fBallX += fBallU;
fBallY += fBallV;
if (fBallY > fMax) {
fBallY = 2 * fMax - fBallY;
fBallV *= -0.8;
}
ball.x = fBallX;
ball.y = fBallY;
// draw a trail
graphics.beginFill(0xafffff, 0.1);
graphics.drawCircle(ball.x, ball.y, fSize);
}
// main update loop
private function update(e:Event):void {
iTime++;
if (iTime < 0) {
// still aiming
UpdateAim();
} else if (iTime == 0) {
// finished aiming so set gun to final position
fBallX = fGunX;
fBallY = fGunY;
fBallU = fSpeed * r2;
fBallV = fSpeed * i2;
// txt.appendText("\n" + fBallU + " " + fBallV);
RotateGun(r2, i2);
} else if (iTime <= iTime2) {
// move missile
UpdatePos();
} else {
// finished
ball.x = tx;
ball.y = ty;
}
}
}
}