elastic collisions

by lukevanin
mass-less elastic collisions taking particle size into consideration. in response to  a question on stackexchange: http://gamedev.stackexchange.com/questions/15911/how-do-i-calculate-the-exit-vectors-of-colliding-projectiles/15925#15925
♥0 | Line 213 | Modified 2011-08-24 22:52:03 | MIT License
play

ActionScript3 source code

/**
 * Copyright lukevanin ( http://wonderfl.net/user/lukevanin )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/rp7P
 */

package 
{
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.LineScaleMode;
    import flash.display.PixelSnapping;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.geom.ColorTransform;
    import flash.geom.Matrix;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    import flash.utils.Dictionary;
    
    /**
     * ...
     * @author Luke Van In
     */
    public class Main extends Sprite 
    {
        private var positions:Vector.<Point>;
        private var velocities:Vector.<Point>;
        private var sprites:Vector.<Sprite>;
        private var radius:Number;
        private var area:Rectangle;
        private var minVelocity:Number;
        private var maxVelocity:Number;
        private var backgroundColor:uint;
        private var colors:Vector.<uint>;
        private var canvas:Sprite;
        private var buffer:BitmapData;
        private var output:BitmapData;
        private var bitmap:Bitmap;
        
        
        public function Main():void 
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
        
        private function init(e:Event = null):void 
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            radius = 20; 
            area = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);
            minVelocity = 1.0;
            maxVelocity = 3.5;
            backgroundColor = 0x000000;
            colors = new <uint>[0x0099FF, 0xFF9900, 0x00FF99, 0x99FF00, 0x9900FF, 0xFF0099];
            canvas = new Sprite();
            buffer = new BitmapData(area.width, area.height, false, backgroundColor);
            output = new BitmapData(area.width, area.height, false, backgroundColor);
            bitmap = new Bitmap(output, PixelSnapping.NEVER, false);
            createParticles(10);
            drawArea();
            addChild(bitmap);
            addEventListener(Event.ENTER_FRAME, frameHandler);
        }
        
        private function drawArea():void
        {
            graphics.beginFill(backgroundColor, 1.0);
            graphics.drawRect(area.x, area.y, area.width, area.height);
            graphics.endFill();
        }
        
        private function createParticles(count:uint):void
        {
            velocities = new Vector.<Point>(count, true);
            positions = new Vector.<Point>(count, true);
            sprites = new Vector.<Sprite>(count, true);
            
            for (var i:int = 0; i < count; i++) {
                var color:uint = colors[i % colors.length ];
                sprites[i] = createSprite(color);
                positions[i] = createNonIntersectingPosition();
                velocities[i] = createVelocity();
            }
        }
        
        private function createSprite(color:uint):Sprite
        {
            var r:Number = radius + 1;
            var output:Sprite = new Sprite();
            output.graphics.beginFill(color, 1.0);
            output.graphics.drawCircle(0, 0, radius);
            output.graphics.endFill();
            canvas.addChild(output);
            return output;
        }
        
        private function createNonIntersectingPosition():Point
        {
            var p:Point = createPosition();
            
            while (hasCollision(p))
                p = createPosition();
                
            return p;
        }
        
        private function hasCollision(a:Point):Boolean
        {
            var r:Number = radius * 2;
            
            for (var i:int = 0, count:int = positions.length; i < count; i++) {
                var b:Point = positions[i];
                
                if (b != null) {
                    if (distance(a, b) < r)
                        return true;
                }
            }
            
            return false;
        }
        
        private function createPosition():Point
        {
            var x:Number = area.x + radius;
            var y:Number = area.y + radius;
            var w:Number = area.width - (radius * 2);
            var h:Number = area.height - (radius * 2);
            var output:Point = new Point();
            output.x = x + (Math.random() * w);
            output.y = y + (Math.random() * h);
            return output;
        }
        
        private function createVelocity():Point
        {
            var f:Number = Math.random() * Math.PI * 2;
            var d:Number = maxVelocity - minVelocity;
            var s:Number = minVelocity + (Math.random() * d);
            var output:Point = new Point();
            output.x = Math.cos(f) * s;
            output.y = Math.sin(f) * s;
            return output;
        }
        
        private function frameHandler(event:Event):void
        {
            checkCollisions();
            updatePositions();
            updateSprites();
            blit();
        }
        
        private function checkCollisions():void
        {
            var r:Number = (radius * 2); 
            var a:Point;
            var b:Point;
            var boundary:Rectangle = new Rectangle(area.x - radius, area.y - radius, area.width + r, area.height + r);
            var count:int = positions.length;
            var d:Number;
            
            for (var i:int = 0; i < count; i++) {
                a = positions[i];
                wrapBoundary(a, boundary)

                for (var j:int = 0; j < i; j++) {
                    b = positions[j];
                    d = distance(a, b);
                    if (d < r) 
                        collideMoving(a, velocities[i], b, velocities[j]);
                }
            }
        }
        
        private function wrapBoundary(p:Point, boundary:Rectangle):void
        {
            if (p.x < boundary.left)
                p.x = boundary.right;
                
            if (p.x > boundary.right)
                p.x = boundary.left;
                
            if (p.y < boundary.top)
                p.y = boundary.bottom;
                
            if (p.y > boundary.bottom)
                p.y = boundary.top;
        }
        
        private function distance(a:Point, b:Point):Number
        {
            var dx:Number = b.x - a.x;
            var dy:Number = b.y - a.y;
            return Math.sqrt((dx * dx) + (dy * dy));
        }
        
        private function collideMoving(pA:Point, vA:Point, pB:Point, vB:Point):void
        {
            // excerpt from http://www.gamasutra.com/view/feature/3015/pool_hall_lessons_fast_accurate_.php?page=3
            
            // First, find the normalized vector n from the center of 
            // circle1 to the center of circle2
            var normal:Point = pA.subtract(pB);
            normal.normalize(1);
            
            // Find the length of the component of each of the movement
            // vectors along n. 
            // a1 = v1 . n
            // a2 = v2 . n
            var tA:Number = dotproduct(vA, normal);
            var tB:Number = dotproduct(vB, normal);

            // Using the optimized version, 
            // optimizedP =  2(a1 - a2)
            //              -----------
            //                m1 + m2
            var optimizedP:Number = (2.0 * (tA - tB)) / 2;

            // Calculate v1', the new movement vector of circle1
            // v1' = v1 - optimizedP * m2 * n
            vA.x = vA.x - (optimizedP * normal.x);
            vA.y = vA.y - (optimizedP * normal.y);
            
            // Calculate v1', the new movement vector of circle1
            // v2' = v2 + optimizedP * m1 * n
            vB.x = vB.x + (optimizedP * normal.x);
            vB.y = vB.y + (optimizedP * normal.y);
        }
        
        private function dotproduct(a:Point, b:Point):Number
        {
            return (a.x * b.x) + (a.y * b.y);
        }
        
        private function updatePositions():void
        {
            var p:Point;
            var v:Point;
            for (var i:int = 0, count:int = velocities.length; i < count; i++) {
                p = positions[i];
                v = velocities[i];
                p.x += v.x;
                p.y += v.y;
            }
        }
        
        private function updateSprites():void
        {
            var sprite:Sprite;
            var p:Point;
            for (var i:int = 0, count:int = positions.length; i < count; i++) {
                p = positions[i];
                sprite = sprites[i];
                sprite.x = p.x;
                sprite.y = p.y;
            }
        }
        
        private function blit():void
        {
            var r:Rectangle = new Rectangle(0, 0, area.width, area.height);
            var p:Point = new Point();
            var m:Matrix = new Matrix();
            var t:ColorTransform = new ColorTransform(1, 1, 1, 0.99, 0, 0, 0, 0);
            output.copyPixels(buffer, r, p);
            output.draw(canvas, m, null, null, r);
            buffer.fillRect(r, backgroundColor);
            buffer.draw(output, m, t, null, r);
        }
    }
    
}

Forked