Python maths

This is kind of a code dump for all code written today, being a vector class, unfinished matrix class and some other Python utilities.

None of this is actually maya dependent and written & tested in Eclipse Indigo with PyDev using Python 3.2.2.

Update October 15: fixed vector/matrix multiplication and added a boundingbox class with helper functions for things as getting all corners as points.

__init__.py

'''
@package: Vmath
@author: Trevor van Hoof

Custom package with mathematical utilities
Requires python.math
'''

listutils.py

'''
@package: Vmath
@author: Trevor van Hoof

List utility functions
'''

def sum( in_list ):
    #collapse the list by adding all values together
    out = 0
    for i in in_list:
        out += i
    return out

def sub( in_list ):
    #collapse the list by subtracting all values from 0
    out = 0
    for i in in_list:
        out -= i
    return out

def avg( in_list ):
    #get the average in a list
    out = 0
    for i in in_list:
        out += i
    return out / len(in_list)

matrix.py

'''
@package: Vmath
@author: Trevor van Hoof

Matrix class: mostly unfinished and untested, but vector multiplication works in Maya
'''

from math import sqrt
from math import sin
from math import cos
from math import radians
from Vmath.vec import Vec
from Vmath import listutils

class Matrix:
    def __init__(self,*args):
        self.data = []
        if not len(args): #nothing given, init a 3x3 unit matrix
            self.data = [1,0,0,0,1,0,0,0,1]
            self.w = 3
            self.h = 3
        elif len(args) in [2,3]:
            self.w = args[0]
            self.h = args[1]
            if len(args) == 2: #given are the dimensions only, init to 0
                self.data = (args[0]*args[1])*[0]
            elif len(args) == 3: #given are the dimensions and one or more default values
                if not hasattr(args[2],'__getitem__'): #not iterable
                    args[2] = [args[2]]
                #iterate & loop args[3] until size x,y is reached
                for y in range(args[1]):
                    for x in range(args[0]):
                        self.data.append( args[2][ (y*args[0]+x)%len(args[2]) ] )
        else: #given are only values, try to establish a matrix of equal dimensions, WARNING: data may be discarded
            if hasattr(args[0],'__getitem__'): #is iterable
                args = args[0]
            d = int(sqrt(len(args)))
            self.w = d
            self.h = d
            for x in range(d):
                for y in range(d):
                    self.data.append(args[x*d+y])
    
    def __getitem__(self,i):
        if hasattr(i,'__getitem__') and len(i) == 2:
            i = i[1] * self.w + i[0]
        return self.data[i]
    
    def __setitem__(self,i,v):
        if len(i) == 2:
            i = i[1] * self.w + i[0]
        self.data[i] = v
        
    def __mul__(self,other):
        if other.__class__ == Vec:
            #vector multiplication
            out = Vec([0]*self.h)
            if len(other) < self.w: #default extra coordinates to 1
                other.extend([1]*(self.w-len(other)))
            for y in range(self.h):
                for x in range(self.w):
                    out[x] += other[y] * self.data[x+y*self.w]
            return out
        
        if other.__class__ == Matrix:
            #allow only matrices of matching size
            #if other.h != self.w and other.w != self.h:
            #    return None
            out = []
            for j in range(self.h):
                for i in range(other.w):
                    othercolumn = []
                    for k in range(other.h):
                        othercolumn.append(other[k*other.w+i])
                    out.append( listutils.sum( Vec(self.data[j*self.w:(j+1)*self.w])*Vec(othercolumn) ) )
                    
            return Matrix(self.h,other.w,out)
                
    @classmethod
    def rotation(cls,rx,ry,rz):
        return cls.rotatez(rz) * cls.rotatey(ry) * cls.rotatex(rx)
        
    @classmethod
    def rotatex(cls,ro):
        ro = radians(ro)
        sx = sin(ro)
        cx = cos(ro)
        return Matrix( [1,0,0,0,cx,-sx,0,sx,cx] )
    
    @classmethod
    def rotatey(cls,ro):
        ro = radians(ro)
        sy = sin(ro)
        cy = cos(ro)
        return Matrix( [cy,0,sy,0,1,0,-sy,0,cy] )
    
    @classmethod
    def rotatez(cls,ro):
        ro = radians(ro)
        sz = sin(ro)
        cz = cos(ro)
        return Matrix( [cz,-sz,0,sz,cz,0,0,0,1] )

    def __repr__(self):
        return str(self.data)

vec.py

'''
@package: Vmath
@author: Trevor van Hoof

Vector class
'''

from __future__ import division
from math import sqrt
from Vmath import listutils

class Vec():
    def __init__(self,*args):
        if len(args) > 1: #init with values given
            self.data = list(args)
        elif hasattr(args[0],'__getitem__'): #init with iterable given
            self.data = args[0]
        else: #init with length given
            self.data = [0]*args[0]

    @classmethod
    def null(cls):
        return cls(0,0,0)
    @classmethod
    def x(cls):
        return cls(1,0,0)
    @classmethod
    def y(cls):
        return cls(0,1,0)
    @classmethod
    def z(cls):
        return cls(0,0,1)

    def __getitem__(self,i):
        return self.data[i]
    
    def __setitem__(self,i,v):
        self.data[i] = v        
        
    def __mul__(self,other):
        other = self._getOtherAsVec(other)
        out = []
        for i in range(len(self.data)):
            out.append(self.data[i]*other.data[i%len(other.data)])
        return Vec(out)
    
    def __div__(self,other):
        return self.__truediv__(other)
    
    def __truediv__(self,other):
        other = self._getOtherAsVec(other)
        out = []
        for i in range(len(self.data)):
            out.append(self.data[i]/other.data[i%len(other.data)])
        return Vec(out)
    
    def __floordiv__(self,other):
        other = self._getOtherAsVec(other)
        out = []
        for i in range(len(self.data)):
            out.append(self.data[i]//other.data[i%len(other.data)])
        return Vec(out)
    
    def __add__(self,other):
        other = self._getOtherAsVec(other)
        out = []
        for i in range(len(self.data)):
            out.append(self.data[i]+other.data[i%len(other.data)])
        return Vec(out)
    
    def __sub__(self,other):
        other = self._getOtherAsVec(other)
        out = []
        for i in range(len(self.data)):
            out.append(self.data[i]-other.data[i%len(other.data)])
        return Vec(out)
    
    def __mod__(self,other):
        other = self._getOtherAsVec(other)
        out = []
        for i in range(len(self.data)):
            out.append(self.data[i]%other.data[i%len(other.data)])
        return Vec(out)
    
    def __neg__(self):
        out = []
        for i in self.data:
            out.append(-i)
        return Vec(out)
    
    def __abs__(self):
        out = []
        for i in self.data:
            if i < 0:
                i = -i
            out.append(i)
        return Vec(out)
        
    def __invert__(self):
        self.data = self.__neg__()
    
    def __lt__(self,other):
        #all values must be less
        other = self._getOtherAsVec(other)
        otherlen = len(other.data) 
        self._lengthWarning(len(self.data),otherlen)
        for i in range(len(self.data)):
            if self.data[i] >= other.data[i%otherlen]:
                return False
        return True
        
    def __gt__(self,other):
        #all values must be greater
        other = self._getOtherAsVec(other)
        otherlen = len(other.data)
        self._lengthWarning(len(self.data),otherlen)
        for i in range(len(self.data)):
            if self.data[i] <= other.data[i%otherlen]:
                return False
        return True
    
    def __eq__(self,other):
        #all values must be equal
        other = self._getOtherAsVec(other)
        if len(other.data) != len(self.data):
            return False
        for i in range(len(self.data)):
            if other.data[i] != self.data[i]:
                return False
        return True
    
    def __le__(self,other):
        other = self._getOtherAsVec(other)
        otherlen = len(other.data)
        self._lengthWarning(len(self.data),otherlen)
        for i in range(len(self.data)):
            if self.data[i] > other.data[i%otherlen]:
                return False
        return True
    
    def __ge__(self,other):
        other = self._getOtherAsVec(other)
        otherlen = len(other.data)
        self._lengthWarning(len(self.data),otherlen)
        for i in range(len(self.data)):
            if self.data[i] < other.data[i%otherlen]:
                return False
        return True
    
    def __ne__(self,other):
        #any value can be not equal
        return not self.__eq__(other)
    
    def _getOtherAsVec(self,other):
        if type(other) != Vec:
            if hasattr(other,'__getitem__'):
                other = Vec(other)
            else:
                other = Vec([other])
        return other
    
    @classmethod
    def getAsVec(self,in_data):
        if type(in_data) != Vec:
            if hasattr(in_data,'__getitem__'):
                out = Vec(in_data)
            else:
                out = Vec([in_data])
        else:
            return in_data
        return out
    
    def _lengthWarning(self,selflen,otherlen):
        if otherlen < selflen:
            print('Warning: dimension mismatch, looping data on right side of Vec < Vec operation')
            #Warning: too little data in X when checking Vec < X, data re-used from the beginning
        elif selflen < otherlen:
            print('Warning: dimension mismatch, right side of Vec < Vec had excessive dimensions ignored')
            #Warning: too much data in X when checking Vec < X, extra data ignored
            
    def __repr__(self):
        return repr(self.data)
    
    def __len__(self):
        return len(self.data)
    
    def append(self,in_data):
        #WARNING: works like list append, changes internal data only
        self.data.append(in_data)
    
    def extend(self,in_data):
        #WARNING: works like list extend, changes internal data only
        self.data.extend(in_data[:])
    
    def normalize(self):
        #WARNING: this changes internal data, to get a copy use normalized()
        self.data = self.normalized().data
    
    def normalized(self):
        out = []
        m = 1 / self.mag()
        for i in self.data:
            out.append(i * m)
        return Vec(out)
    
    def mag(self):
        return sqrt(self.sqr())
    
    def length(self):
        return self.mag()
        
    def sqr(self):
        out = 0
        for i in self.data:
            out += i*i
        return out
    
    def dot(self,other):
        out = self.__mul__(other)
        return listutils.sum( out.data )
    
    def cross(self,other):
        other = self._getOtherAsVec(other)
        out = [self.data[1] * other.data[2] - other.data[1] * self.data[2],
               self.data[2] * other.data[0] - other.data[2] * self.data[0],
               self.data[0] * other.data[1] - other.data[0] * self.data[1]]
        return Vec(out)

print(Vec(1,1,1) >= Vec(0,0,0))

boundingbox.py

'''
@package: Vmath
@author: Trevor van Hoof

BoundingBox class
'''
from Vmath.vec import Vec


class BoundingBox:
    '''
    BoundingBox class, may be object space
    '''
    def __init__(self, in_min, in_max):
        self.min = Vec.getAsVec(in_min)
        self.max = Vec.getAsVec(in_max)
        if len(self.min) != len(self.max):
            raise ValueError('Bounding box min/max points have a different\
                              number of dimensions; creation failed')
    
    def cornerPoints(self):
        x = [self.min[0],self.max[0]]
        y = [self.min[1],self.max[1]]
        z = [self.min[2],self.max[2]]
        out = []
        for j in range(2):
            for i in range(4):
                out.append( Vec(x[int(i*0.5)], y[bool(i%3)], z[j]) )
        return out
    
    def center(self):
        return (self.max-self.min)*0.5+self.min
    
    def width(self):
        return self.max[0]-self.min[0]
        
    def height(self):
        return self.max[1]-self.min[1]
    
    def depth(self):
        return self.max[2]-self.min[2]
    
    def dimensions(self):
        return self.max-self.min
    
    def contains(self, in_pt):
        pt = Vec.getAsVec(in_pt)
        
        if pt > self.min and pt < self.max:
            return True
        return False
    
    def extend(self, in_pt):
        pt = Vec.getAsVec(in_pt)
        ptlen = len(pt) 

        for i in range(len(self.min)):
            if self.min[i] > pt[i%ptlen]:
                self.min[i] = pt[i%ptlen]
            elif self.max[i] < pt[i%ptlen]:
                self.max[i] = pt[i%ptlen]
    
    def asWorldSpace(self):
        '''
        Returns the world space bounding box of this bounding box
        Return value may be identical if data already was in world space
        '''
        outmin = []
        outmax = []
        for pt in self.cornerPoints():
            if not outmin:
                outmin = pt
                outmax = pt
                continue
            for i in range(3):
                if pt[i] < outmin[i]:
                    outmin[i] = pt[i]
                if pt[i] > outmax[i]:
                    outmax[i] = pt[i]
        return BoundingBox(outmin,outmax)
    
    def __repr__(self):
        return repr(self.asList())
    
    def asList(self):
        tmp = self.min[:]
        tmp.extend(self.max)
        return tmp

Leave a Reply

Your email address will not be published. Required fields are marked *