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 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;
            }
        }
    }
}