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 {
private const fSize:Number = 8.0;
private const fSpeed:Number= 10.0;
private const fGunX:Number = 250.0;
private const fGunY:Number = 350.0;
private const fInitX:Number =400.0;
private const fInitY:Number =400.0;
private const fSpeed2:Number= fSpeed * fSpeed;
// gravity: positive as y measured downwards. Adjusted for the frame rate
private var fGravity:Number = 10 / stage.frameRate;
private var gun1:Bitmap, gun2:Bitmap;
private var ball1:Shape, ball2:Shape, target:Shape;
private var vx1:Number, vy1:Number;
private var vx2:Number, vy2:Number;
private var tx:Number, ty:Number;
private var fTime1:Number, fTime2:Number, fTime:Number;
private var txt: TextField;
public function Main():void {
stage.addEventListener(MouseEvent.MOUSE_DOWN, click);
txt = new TextField();
txt.width = txt.height = 465;
txt.textColor = 0xffffff;
addChild(txt);
addEventListener(Event.ENTER_FRAME, update);
MakeThings();
init();
}
private function MakeThings():void {
var data:BitmapData;
ball1 = new Shape();
ball1.graphics.beginFill(0xffffaf);
ball1.graphics.drawCircle(0, 0, fSize);
ball1.graphics.endFill();
addChild(ball1);
ball2 = new Shape();
ball2.graphics.beginFill(0xafffff);
ball2.graphics.drawCircle(0, 0, fSize);
ball2.graphics.endFill();
addChild(ball2);
target = new Shape();
target.graphics.lineStyle(1, 0xff8000);
target.graphics.drawCircle(0, 0, fSize);
addChild(target);
data = new BitmapData(fSize * 3, fSize);
data.fillRect(data.rect, 0xffffffaf);
gun1 = new Bitmap(data);
addChild(gun1);
data = new BitmapData(fSize * 3, fSize);
data.fillRect(data.rect, 0xffafffff);
gun2 = new Bitmap(data);
addChild(gun2);
}
private function DrawCoverage():void {
// draw the border of the region shots can reach.
// Note that the upper missile path always touches this
var iX:int, fDist:Number; fSpeed2:Number;
graphics.beginFill(0x804040);
for (iX = 0; iX < stage.stageWidth; iX++) {
fDist = iX - fGunX;
graphics.drawRect(iX, fGunY +
0.5 * (fGravity * fDist * fDist / fSpeed2 - fSpeed2 / fGravity),
1, 1);
}
// draw trajectories of missiles fired horizontally from gun.
// The code tests against this to decide whether to shoot up or down.
// the two curves are the same, separated the maximum height of shots
graphics.beginFill(0x404040);
for (iX = 0; iX < stage.stageWidth; iX++) {
fDist = iX - fGunX;
graphics.drawRect(iX, fGunY +
0.5 * (fGravity * fDist * fDist / fSpeed2),
1, 1);
}
}
private function RotateGun(gun:Bitmap, x:Number, y:Number):void {
gun.transform.matrix = new Matrix(x, y, -y, x, fGunX + y * fSize / 2, fGunY - x * fSize / 2);
}
private function init(_x:Number = fInitX, _y:Number = fInitY):void {
var fDist:Number, fHeight:Number, fGravity2:Number,
fDisc:Number, fB:Number;
// entry point
// create a ball to throw
// create the target
graphics.clear();
graphics.beginFill(0);
graphics.drawRect(0, 0, 500, 500);
DrawCoverage();
txt.text = "";
target.x = tx = _x;
target.y = ty = _y;
graphics.beginFill(0x404040, 1);
graphics.drawCircle(fGunX, fGunY, fSize);
ball1.x = ball2.x = fGunX;
ball1.y = ball2.y = fGunY;
// work out the velocity
// get the range
fDist = tx - fGunX;
fHeight = ty - fGunY;
// get the discriminant
fGravity2 = fGravity * fGravity;
fB = fSpeed2 + fHeight * fGravity;
fDisc = fB * fB - fGravity2 * (fDist * fDist + fHeight * fHeight);
// if the discriminant is negative then cannot solve/outside of coverage area
if (fDisc < 0) {
fTime = 1000;
txt.appendText("Too far/too high for launch speed");
return;
}
fDisc = Math.sqrt(fDisc);
// calculate the times and velocities
// first solution: shorter time, faster/more direct shot
fTime1 = Math.sqrt(2 * (fB - fDisc) / fGravity2);
vx1 = fDist / fTime1;
vy1 = -Math.sqrt(fSpeed2 - vx1 * vx1);
// check in case below height where need to aim downwards
if (2 * fHeight > fDist * fDist * fGravity / fSpeed2) {
vy1 =-vy1;
}
RotateGun(gun1, vx1 / fSpeed, vy1 / fSpeed);
txt.appendText ("\n'angle' 1: " +
String( 180 / Math.PI * Math.atan2(vx1, -vy1)));
// second solution: longer time, higher/looping shot
fTime2 = Math.sqrt(2 * (fB + fDisc) / fGravity2);
vx2 = fDist / fTime2;
vy2 = -Math.sqrt(fSpeed2 - vx2 * vx2);
RotateGun(gun2, vx2 / fSpeed, vy2 / fSpeed);
txt.appendText ("\n'angle' 2: " +
String( 180 / Math.PI * Math.atan2(vx2, -vy2)));
fTime = 0;
}
private function click(e:MouseEvent):void {
init(e.stageX, e.stageY);
}
private function update(e:Event):void {
fTime++;
if (fTime < fTime1) {
update1();
} else {
ball1.x = tx;
ball1.y = ty;
}
if (fTime < fTime2) {
update2();
} else {
ball2.x = tx;
ball2.y = ty;
}
}
private function update1():void {
ball1.x = fGunX + vx1 * fTime;
ball1.y = fGunY + vy1 * fTime + 0.5 * fGravity * fTime * fTime;
graphics.beginFill(0xffffaf, 0.1);
graphics.drawCircle(ball1.x, ball1.y, fSize);
}
private function update2():void {
ball2.x = fGunX + vx2 * fTime;
ball2.y = fGunY + vy2 * fTime + 0.5 * fGravity * fTime * fTime;
graphics.beginFill(0xafffff, 0.1);
graphics.drawCircle(ball2.x, ball2.y, fSize);
}
}
}