In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

Ballistics 3

http://johnblackburne.blogspot.com/2012/01/trig-free-rotation-blending.html
http://johnblackburne.blogspot.com/2012/01/varying-rotation-speed.html
http://johnblackburne.blogspot.com/2012/01/variable-speed-rotation-with-complex.html
http://johnblackburne.blogspot.com/2012/02/quadratic-curve-through-three-points.html
// 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;
        private var fMinEaseAngle:Number;

        // game objects
        private var gun:Bitmap;
        private var ball:Shape, target:Shape;
        private var txt:TextField;
        
        // 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;
        // (rDelta2, iDelta2), smalle rotation
        private var rDelta2:Number, iDelta2:Number;

        // (rStep, iStep), rotation per frame + direction
        private var rStep:Number, iStep:Number;
        // (rStep2, iStep2), small rotation per frame + direction
        private var rStep2:Number, iStep2: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:Number, iTime2:Number, iTime:Number;
        
        // whether to rotate at fixed speed (with or without ease-in)
        // or in a fixed time
        private var iSpeedMode:int;
        // set when easing in
        private var bEasing:Boolean;
        
        private const eConstant:int        = 0;
        private const eFixedTime:int       = 1;
        private const eEaseIn:int          = 2;
        private const eNumModes:int        = 3;
        
        private const aModeStr:Vector.<String> = Vector.<String>([
            "fixed rotation speed",
            "fixed rotation time",
            "fixed rotation speed with ease-in",
        ]);
        private const aModeChangeStr:Vector.<String> = Vector.<String>([
            "\nfixed speed rotation enabled",
            "\nvariable speed rotation enabled",
            "\nfixed speed rotation with ease-in enabled",
        ]);
        
        private const aColours:Vector.<int> = Vector.<int>([
            0x804040,
            0x408040,
            0x404080,
        ])
        
        // 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;
            fMinEaseAngle = stepAngle * 15;
            
            iSpeedMode = eConstant;

            rDelta = Math.cos(stepAngle);
            iDelta = Math.sin(stepAngle);

            rDelta2 = Math.cos(stepAngle / 30);
            iDelta2 = Math.sin(stepAngle / 30);

            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, aColours[iSpeedMode]);
                
            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);

            //var iX:int, fDist:Number;
            //graphics.beginFill(aColours[iSpeedMode]);
            //for (iX = 0; iX < stage.stageWidth; iX++) {
            //    fDist = iX - fGunX;
            //    graphics.drawRect(iX, fGunY +
            //        0.5 * (fGravity * fDist * fDist / fSpeed2 - fSpeed2 / fGravity),
            //        1, 1);
            //}
            
        }

        // 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);

            // if the discriminant is negative then
            // cannot solve, which means target is outside of coverage area
            if (fDisc < 0) {
                txt.text = "Too far/too high for launch speed";

                // set the time to something big, to stop updates
                iTime = 1000;

                // use click outside coverage to toggle varspeed
                iSpeedMode = (iSpeedMode + 1) % eNumModes;

                txt.appendText(aModeChangeStr[iSpeedMode]);
                DrawCoverage();

                return;
            }

            DrawCoverage();
            graphics.lineStyle();
            txt.text = aModeStr[iSpeedMode];

            // 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 ("\nangle from: " + String(180 / Math.PI * fAngle1));
            txt.appendText ("\nangle to: " + String(180 / Math.PI * fAngle2));

            bEasing = false;

            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;
        }
        
        // set up gun aiming
        private function AimGun():void {
            
            var fAngle:Number = fAngle2 - fAngle1;
            
            if (iSpeedMode == eEaseIn &&
                    Math.abs(fAngle) > fMinEaseAngle &&
                    !bEasing) {
                if (fAngle2 > fAngle1) {
                    rStep2 = rDelta2;
                    iStep2 = iDelta2;
                } else {
                    rStep2 = rDelta2;
                    iStep2 = -iDelta2;
                }
                rStep = rStep2; iStep = iStep2;
                iTime1 = 31;
                bEasing = true;

            } else if (iSpeedMode == eFixedTime) {
                // fixed time step, varying speed
                // need to recalculated the step
                var fStep:Number = fAngle / 30;
                rStep = Math.cos(fStep);
                iStep = Math.sin(fStep);
                iTime1 = 31;
                bEasing = false;

            } else {
                // 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;
                bEasing = false;
            }

            // rotate gun to current direction
            RotateGun(r1, i1);
        }

        // update aim direction
        private function UpdateAim():void {
            var fR:Number, fI:Number;
            
            if (bEasing) {
                fR = rStep; fI = iStep;
                rStep = fR * rStep2 - fI * iStep2;
                iStep = fI * rStep2 + fR * iStep2;
            }
                
            // 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 {
            // use equations of motion to update its position
            ball.x = fGunX + fSpeed * r2 * iTime;
            ball.y = fGunY + fSpeed * i2 * iTime +
                    0.5 * fGravity * iTime * iTime;
            // 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) {
                if (bEasing) {
                    fAngle1 = Math.atan2(r1, -i1);
                    AimGun();
                    iTime = -iTime1;
                    UpdateAim();
                } else {
                    // finished aiming so set gun to final position
                    RotateGun(r1, i1);
                }
            } else if (iTime <= iTime2) {
                // move missile
                UpdatePos();
            } else {
                // finished
                ball.x = tx;
                ball.y = ty;
            }
        }
    }
}