#!/usr/bin/python
# vector.py $Revision: 1.3 $ $Date: 2005/10/12 01:36:07 $
# This library contains the class that defines what exactly a vector is
# and what can be done with it. Vectors, by the way, are synonymous
# with 'point', but vector is used to avoid confusion with the point
# entity type.
# Also in this file is a special kind of list designed to hold
# collections of vectors.

RESOLUTION= .000001 # If a coord differs by < this, then consider ==.
import math

# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class Vector:
    """This class defines what a vector is and how it works."""
    lastIDused= 0 # Class static variable, new id pool.
    def __init__(self, xin=0, yin=0, zin=0):
        Vector.lastIDused += 1 # Increment last ID used...
        self.p_id= Vector.lastIDused #...which becomes the ID.
        self.x, self.y, self.z= xin, yin, zin # Coordinate assignment.
        self.refcount= 0 # It exists, but it's not deployed yet.
    def __eq__(self, other): # Overload == operator.
        if math.fabs(self.x - other.x) < RESOLUTION:
            if math.fabs(self.y - other.y) < RESOLUTION:
                if math.fabs(self.z - other.z) < RESOLUTION:
                    return True # Practically identical!
        return False # Something's not the same.
    def __ne__(self, other): # Overload != operator.
        return not __eq__(self, other)
    def __gt__(self, other):
        if self.x > other.x: # First priority is x...
            return True
        else:
            return False
        if self.y > other.y: # x must be ==, second priority y...
            return True
        else:
            return False
        if self.z > other.z: # x and y must be ==, thrid priority z...
            return True
        else:
            return False
        return False # If here, then the points are equal in x, y, z.
    def __lt__(self, other):
        if self.__eq__(other): # Are they equal?
            return False # That's no good.
        return not self.__gt__(other)
    def __ge__(self, other):
        if self.__eq__(other): # Are they equal?
            return True # That's ok now.
        return self.__gt__(other)
    def __le__(self, other):
        if self.__eq__(other): # Are they equal?
            return True # That's ok now.
        return self.__lt__(other)
    def __cmp__(self, other):
        if self.__eq__(other): # Are they equal?
            return 0
        if self.__gt__(other): # Greater?
            return 1
        else: # Must be less than.
            return -1
    def __repr__(self):
        return 'Vector...['+str(self.x)+', '+str(self.y)+', '+str(self.z)+']'
    def __neg__(self): # -P 
        return Vector(-self.x, -self.y, -self.z)
    def set2(self, xin, yin, zin): # AKA edit location.
        x, y, z= xin, yin, zin # Coordinate assignment.
    def magnitude(self):
        return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
    def distance(self, otherpt):
        dif_vector= self - otherpt # Creates a new vector.
        return dif_vector.magnitude()
    def direction(self): # Produces a direction vector.
        mag= self.magnitude()
        unit= Vector() # Unit vector has length of 1.
        if mag > 0:
            unit.x= self.x / mag
            unit.y= self.y / mag
            unit.z= self.z / mag
        else:
            unit.set2(0,0,0)
        return unit
    def __add__(self, offset): # +
        sum= Vector(self.x+offset.x, self.y+offset.y, self.z+offset.z)
        return sum
    def __sub__(self, subtracted): # -
        return self + -subtracted
    def rotate(self, ang, basepoint=None): # On xy plane (%)
        rotated= Vector()
        ang= math.radians(ang) # Convert to radians, input in degrees.
        if basepoint:
            rotated= (self - basepoint).rotate(ang) # Recursive call.
        else: 
            s= math.sin(ang)
            c= math.cos(ang)
            rotated.x= self.x*c - self.y*s
            rotated.y= self.y*c + self.x*s
            rotated.z= self.z
        return rotated
    def scale(self, fact, basepoint= None): 
        #if fact is a vector: uniform else: more complicatd.
        scaled= self.copy()
        if basepoint: # If base supplied, offset and try again.
            scaled= (scaled - basepoint).scale(fact) # Recursive call.
        else: # No basepoint, scale around zero.
            if type(fact) is type(scaled): # Is the factor a vector?
                scaled.x *= fact.x  # Separate scales.
                scaled.y *= fact.y
                scaled.z *= fact.z
            else:
                scaled.x *= fact 
                scaled.y *= fact 
                scaled.z *= fact 
        return scaled
    def __mul__(self, B):
        return self.scale(B)
    def dot(self, B): # |
        return self.x*B.x + y*B.y + z*B.z
    def angle(self, B):
        mm= self.magnitude() * B.magnitude()
        ang= 0
        if mm:
            ang= acos( self.dot(B) / mm )
        return ang
    def midpoint(self, B):
        D= self - B
        scaled= D * .5
        reset = scaled + B
        return reset
#        return (((self - B) * .5 ) + B)
    def cross(self, B): # &
        C= Vector()
        C.x= self.y * B.z - self.z * B.y
        C.y= self.z * B.x - self.x * B.z
        C.z= self.x * B.y - self.y * B.x
        return C
    def copy(self):
        return Vector(self.x, self.y, self.z)

# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class VectorList:
    """This is a special container class used for holding and
    manipulating points."""
    def __init__(self, starterlist=list()):
        self.thelist= starterlist # The underlying list that holds everything.
    def nearest(self, Pin, sense=True): # False sense means farthest.
        if not self.thelist:
            return None
        winner= self.thelist[0]
        d= Pin.distance(winner)
        for P in self.thelist[1:]:
            dnew= Pin.distance(P)
            if (sense and dnew < d) or (not sense and dnew > d):
                d= dnew
                winner= P
        return winner
    def farthest(self, Pin):
        return self.nearest(Pin, False) # Change sense.
    def __contains__(self, P): # Overload: if P in self:
        if not self.thelist:
            return False
        nearest= self.nearest(P)
        if nearest == P: return True
        else: return False
    def __getitem__(self, k): # k is index or slice object.
        """Should accomodate slicing."""
        return self.thelist[k]
    def __delitem__(self, k): # k is index or slice object.
        """Should accomodate slicing."""
        for V in self.thelist[k]:
            self.remove(V)
    def not__contains__(self, P): # Overload: if P in self:
        nearest= self.nearest(P)
        if nearest == P: return False
        else: return True
    def append(self,P):
        if P not in self:
            self.thelist.append(P)
        P.refcount += 1 # Always increment reference counter.
    def remove(self,P):
        if P in self:
            if P.refcount > 1: # In use elsewhere.
                P.refcount -= 1 # Decrement refcount and leave alone.
            else:
                self.thelist.remove(P)

# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def test_point_facilities():
    A= Vector(3, 3, 3)
    B= Vector(1, 2, 3)
    ORIGIN= Vector()
    C= A + B
    D= A - B
    print 'Sum A+B:'+ str(C)
    print 'Diff A-B:'+ str(D)
    print 'midpoint of C and origin Z:' + str(C.midpoint(ORIGIN))
    R= A.rotate(math.pi/2)
    print 'Rotate A by pi/2:' + str(R)
    D1= A.direction()
    D2= R.direction()
    print 'Direction of A' + str(D1)
    print 'Direction of R' + str(D2)
    M1= D1.magnitude()
    print 'Magnitude of direction of A:' + str(M1)
    print 'Magnitude of direction of B:' + str(B.magnitude())

    pl= VectorList([A, B, C, D, R, D1, D2])
    if B in pl:
        print "B is in the new list."
    BB= B * 99.0
    if BB in pl:
        print "BB is in the new list."
    else:
        print "This is correct, BB not in list."
    print "Farthest from BB:"+str(pl.farthest(BB))
    print "Try slicing:"+str( pl[-1] )

#test_point_facilities()
