'''Module for physics simulation.

This module provides two classes that allow the
approximation of physics behind bouncing balls.'''

################################################################################

__version__ = '$Revision: 0 $'
__date__ = 'February 20, 2007'
__author__ = 'Stephen "Zero" Chappell <my.bios@gmail.com>'
__credits__ = '''\
S. Schaub, for introducing me to programming.
B. Brown, for teaching me some math courses.
C. Parker, for freely providing boids pseudocode.'''

################################################################################

import math as _math
import sys as _sys

################################################################################

class Vector:

    'Vector(x, y) -> Vector'

    def __init__(self, x, y):
        'Initialize the Vector object.'
        self.x = float(x)
        self.y = float(y)

    def __repr__(self):
        'Return the vector\'s representation.'
        return 'Vector(%r, %r)' % (self.x, self.y)

    def __add__(self, vector):
        'Return the sum of vector addition.'
        return Vector(self.x + vector.x, self.y + vector.y)

    def __sub__(self, vector):
        'Return the difference of vector subtraction.'
        return Vector(self.x - vector.x, self.y - vector.y)

    def __mul__(self, number):
        'Return the product of vector multiplication.'
        return Vector(self.x * number, self.y * number)

    def __div__(self, number):
        'Return the quotient of vector division.'
        return Vector(self.x / number, self.y / number)

    def __iadd__(self, vector):
        'Execute addition in-place.'
        self.x += vector.x
        self.y += vector.y
        return self

    def __isub__(self, vector):
        'Execute subtraction in-place.'
        self.x -= vector.x
        self.y -= vector.y
        return self

    def __imul__(self, number):
        'Execute multiplication in-place.'
        self.x *= number
        self.y *= number
        return self

    def __idiv__(self, number):
        'Execute division in-place.'
        self.x /= number
        self.y /= number
        return self

    def __abs__(self):
        'Return the vector\'s magnitude.'
        return _math.hypot(self.x, self.y)

    def unit(self):
        'Return the unit vector.'
        return self / abs(self)

################################################################################

class Ball:

    'Ball(x, y, radius) -> Ball'

    def __init__(self, x, y, radius):
        'Initialize the Ball object.'
        self.pos = Vector(x, y)
        self.vel = Vector(0, 0)
        self.err = Vector(0, 0)
        self.rad = radius

    def crash(self, ball):
        'Try to crash two balls together.'
        p = ball.pos - self.pos
        a = abs(p)
        if a <= self.rad + ball.rad:
            v = self.vel - ball.vel
            s = _sub(_ang(p), _ang(v))
            if s < _PI_D_2:
                e =  p * (_math.cos(s) * abs(v) / a)
                ball.err += e
                self.err -= e

    def move(self, frames_per_second):
        'Update the ball\'s position.'
        self.vel += self.err
        self.err = Vector(0, 0)
        self.pos += self.vel / frames_per_second

################################################################################

_PI_M_2 = _math.pi * 2
_PI_D_2 = _math.pi / 2

################################################################################

def _ang(vector):
    'Private module function.'
    return _math.atan2(vector.x, vector.y) % _PI_M_2

def _sub(angle_a, angle_b):
    'Private module function.'
    diff = abs(angle_a - angle_b)
    return _PI_M_2 - diff if diff > _math.pi else diff

################################################################################

if __name__ == '__main__':
    _sys.stdout.write('Content-Type: text/plain\n\n')
    _sys.stdout.write(file(_sys.argv[0]).read())
