[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

Re: [pygame] Google Summer of Code participation



Marcus von Appen wrote:
On, Thu Mar 12, 2009, Iuri wrote:

[...]
In the wiki we have 2DVectorClass and 3DVectorClass classes. Why these
classes are not so good? Or what I can improve on that?

They are good, but in Python, making them slower than a good and robust
C implementation.
What other classes are good to add to pygame? Matrix, quaternion, what else?

Space transformations (eclidean, hyperbolic, etc.), Tensors, .... The focus of that task lies on a fast and robust implementation and API,
not on tons of features. However, vectors, matrices and quaternions
already offer a very big field for further development (equotation
solvings, 2d/3d/4d transformations, agrange...).

Regards
Marcus


I also find this proposal very interesting. I don't know if I will apply for GSoC, yet.
In any case, I find myself needing Vector classes and the sort all the time so I've been
thinking of implementing a Extension type for some time. I actually started some work
just this week without knowing about the possible pygame entry for GSoC.

So in any case I'll just throw my work out there and if someone gets accepted for this
project they might start to work from here.

I probable should put this into some svn repo but that'll have to wait because I'll be
gone for at least the next week, but I wanted you to have this. I'll continue the work
when I return.

For now there is only a Vector3d type with some rather simple operations.

Please consider this Work-In-Progress.

Disclaimer: This is the first time I wrote a C extension for python so there might be
some tricks or conventions that I'm not aware of. But I tried to adhere to the Python
Style Guide (http://www.python.org/dev/peps/pep-0007/).


So happy coding and see you around.

best wishes
Lorenz


PS: I'm also interested in the AI Project especially pathfinding, but I feel that the
Vector class has a higher priority.

from distutils.core import setup, Extension
setup(name="vector", version="0.1",
      ext_modules=[Extension("vector", ["vector.c"])])
/**************************************************************
 * Implementation of a Vector type
 * written by Lorenz Quack, March 2009
 * Use as you wish.
 **************************************************************/


#include <Python.h>
#include "structmember.h"
#include <math.h>
#include <float.h>
#include <wchar.h>
#include <stdio.h>

#define Vector3d_Check(v)  PyObject_TypeCheck(v, &Vector3d_Type)

/********************************
 * Object definition
 ********************************/

typedef struct {
    PyObject_HEAD
    /* Type-specific fields go here. */
    double x, y, z;
    double epsilon;
} Vector3d;


static PyMemberDef Vector3d_members[] = {
    {"x", T_DOUBLE, offsetof(Vector3d, x), 0,
     "x component"},
    {"y", T_DOUBLE, offsetof(Vector3d, y), 0,
     "y component"},
    {"z", T_DOUBLE, offsetof(Vector3d, z), 0,
     "z component"},
    {"epsilon", T_DOUBLE, offsetof(Vector3d, epsilon), 0,
     "small value used in comparisons"},
    {NULL}  /* Sentinel */
};

/********************************
 * Forward declarations
 ********************************/

static PyTypeObject Vector3d_Type;

static int checkVector3dCompatible(PyObject *obj);
static int checkRealNumber(PyObject *obj);
static double sequence_GetItem_AsDouble(PyObject *seq, Py_ssize_t index);

static PyObject *Vector3d_NEW(const double x, const double y, const double z);
static PyObject *Vector3d_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
static void Vector3d_dealloc(Vector3d* self);
static int Vector3d_init(Vector3d *self, PyObject *args, PyObject *kwds);

static PyObject *Vector3d_add(PyObject *o1, PyObject *o2);
static PyObject *Vector3d_inplace_add(Vector3d *self, PyObject *other);
static PyObject *Vector3d_sub(PyObject *o1, PyObject *o2);
static PyObject *Vector3d_inplace_sub(Vector3d *self, PyObject *other);
static PyObject *Vector3d_mul(PyObject *o1, PyObject *o2);
static PyObject *Vector3d_div(Vector3d *self, PyObject *other);
static PyObject *Vector3d_inplace_div(Vector3d *self, PyObject *other);
static PyObject *Vector3d_floor_div(Vector3d *self, PyObject *other);
static PyObject *Vector3d_inplace_floor_div(Vector3d *self, PyObject *other);
static PyObject *Vector3d_neg(Vector3d *self);
static PyObject *Vector3d_pos(Vector3d *self);
static int Vector3d_nonzero(Vector3d *self);

static PyObject *Vector3d_len(PyObject *self);
static PyObject *Vector3d_GetItem(Vector3d *self, Py_ssize_t index);
static int Vector3d_SetItem(Vector3d *self, Py_ssize_t index, PyObject *value);

static PyObject *Vector3d_getLength(Vector3d *self, void *closure);
static int Vector3d_setLength(Vector3d *self, PyObject *newLength, void *closure);
static PyObject *Vector3d_getLengthSquared(Vector3d *self, void *closure);
static int Vector3d_setLengthSquared(Vector3d *self, PyObject *newLengthSquared, void *closure);
static PyObject *Vector3d_getTheta(Vector3d *self, void *closure);
static int Vector3d_setTheta(Vector3d *self, PyObject *newTheta, void *closure);
static PyObject *Vector3d_getPhi(Vector3d *self, void *closure);
static int Vector3d_setPhi(Vector3d *self, PyObject *newPhi, void *closure);

static PyObject *Vector3d_rotated(Vector3d *self, PyObject *args);
static PyObject *Vector3d_rotate(Vector3d *self, PyObject *args);
static PyObject *Vector3d_rotatedX(Vector3d *self, PyObject *angleObject);
static PyObject *Vector3d_rotateX(Vector3d *self, PyObject *angleObject);
static PyObject *Vector3d_rotatedY(Vector3d *self, PyObject *angleObject);
static PyObject *Vector3d_rotateY(Vector3d *self, PyObject *angleObject);
static PyObject *Vector3d_rotatedZ(Vector3d *self, PyObject *angleObject);
static PyObject *Vector3d_rotateZ(Vector3d *self, PyObject *angleObject);
static PyObject *Vector3d_normalize(Vector3d *self);
static PyObject *Vector3d_normalized(Vector3d *self);
static PyObject *Vector3d_isNormalized(Vector3d *self);
static PyObject *Vector3d_cross(Vector3d *self, PyObject *other);
static PyObject *Vector3d_dot(Vector3d *self, PyObject *other);
static PyObject *Vector3d_angleTo(Vector3d *self, PyObject *other);

static PyObject *Vector3d_repr(Vector3d *self);
static PyObject *Vector3d_str(Vector3d *self);
static PyObject *Vector3d_richcompare(Vector3d *self, PyObject *other, int op);


/********************************
 * Helper functions
 ********************************/
static int 
checkVector3dCompatible(PyObject *obj)
{
    int i;
    PyObject *tmp;
    if (Vector3d_Check(obj)) {
        return 1;
    }
    if (!PySequence_Check(obj)) {
        return 0;
    }
    if (PySequence_Length(obj) != 3) {
        return 0;
    }

    for (i = 0; i < 3; ++i) {
        tmp = PySequence_GetItem(obj, i);
        if (!checkRealNumber(tmp)) {
            Py_DECREF(tmp);
            return 0;
        }
        Py_DECREF(tmp);
    }
    return 1;
}

static int
checkRealNumber(PyObject *obj)
{
    if (PyNumber_Check(obj)) {
        if (!PyComplex_Check(obj)) {
            return 1;
        }
    }
    return 0;
}

static double
sequence_GetItem_AsDouble(PyObject *seq, Py_ssize_t index)
{
    PyObject *item;
    double value;
    assert(PySequence_Check(seq));
    item = PySequence_GetItem(seq, index);
    value = PyFloat_AsDouble(item);
    Py_DECREF(item);
    return value;
}

/**********************************
 * constructor and destructor
 **********************************/

static PyObject *
Vector3d_NEW(const double x, const double y, const double z)
{
    Vector3d *object = PyObject_New(Vector3d, &Vector3d_Type);
    if (object != NULL) {
        object->x = x;
        object->y = y;
        object->z = z;
        object->epsilon = FLT_EPSILON;
    } 
    else {
        fprintf(stderr, "FAILURE: could not create new Vector3d object!\n");
    }
    return (PyObject *)object;
}

static PyObject *
Vector3d_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    Vector3d *self = (Vector3d *)type->tp_alloc(type, 0);

    if (self != NULL) {
        self->epsilon = FLT_EPSILON;
    }

    return (PyObject *)self;
}

static void
Vector3d_dealloc(Vector3d* self)
{
    self->ob_type->tp_free((PyObject*)self);
}

static int
Vector3d_init(Vector3d *self, PyObject *args, PyObject *kwds)
{
    PyObject *xOrSequence=NULL, *y=NULL, *z=NULL;
    static char *kwlist[] = {"x", "y", "z", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOO", kwlist,
                                      &xOrSequence, &y, &z))
        return -1;

    if (xOrSequence) {
        if (checkRealNumber(xOrSequence)) {
            self->x = PyFloat_AsDouble(xOrSequence);
        } 
        else if (Vector3d_Check(xOrSequence)) {
            self->x = ((Vector3d*)xOrSequence)->x;
            self->y = ((Vector3d*)xOrSequence)->y;
            self->z = ((Vector3d*)xOrSequence)->z;
            return 0;
        } 
        else if (checkVector3dCompatible(xOrSequence)) {
            self->x = sequence_GetItem_AsDouble(xOrSequence, 0);
            self->y = sequence_GetItem_AsDouble(xOrSequence, 1);
            self->z = sequence_GetItem_AsDouble(xOrSequence, 2);
            return 0;
        } 
        else {
            PyErr_SetString(PyExc_ValueError,
                            "Vector3d must be initialized with 3 real numbers or a sequence of 3 real numbers");
            return -1;
        }
    } 
    else {
        self->x = 0.;
    }

    if (y) {
        if (checkRealNumber(y)) {
            self->y = PyFloat_AsDouble(y);
        } 
        else {
            PyErr_SetString(PyExc_ValueError,
                            "Vector3d must be initialized with 3 real numbers or a sequence of 3 real numbers");
            return -1;
        }
    } 
    else {
        self->y = 0.;
    }

    if (z) {
        if (checkRealNumber(z)) {
            self->z = PyFloat_AsDouble(z);
        } 
        else {
            PyErr_SetString(PyExc_ValueError,
                            "Vector3d must be initialized with 3 real numbers or a sequence of 3 real numbers");
            return -1;
        }
    } 
    else {
        self->z = 0.;
    }
    return 0;
}


/************************************
 * Emulation routines
 ************************************/


static PyObject *
Vector3d_add(PyObject *o1, PyObject *o2)
{
    if (!checkVector3dCompatible(o1)) {
        return Py_NotImplemented;
    }
    if (!checkVector3dCompatible(o2)) {
        return Py_NotImplemented;
    }

    if (Vector3d_Check(o1)) {
        if (Vector3d_Check(o2)) {
            return Vector3d_NEW(((Vector3d*)o1)->x + ((Vector3d*)o2)->x, 
                                ((Vector3d*)o1)->y + ((Vector3d*)o2)->y, 
                                ((Vector3d*)o1)->z + ((Vector3d*)o2)->z);
        } 
        else {
            double o2_x = sequence_GetItem_AsDouble(o2, 0);
            double o2_y = sequence_GetItem_AsDouble(o2, 1);
            double o2_z = sequence_GetItem_AsDouble(o2, 2);
            return Vector3d_NEW(((Vector3d*)o1)->x + o2_x, 
                                ((Vector3d*)o1)->y + o2_y, 
                                ((Vector3d*)o1)->z + o2_z);
        }
    } 
    else {
        double o1_x = sequence_GetItem_AsDouble(o1, 0);
        double o1_y = sequence_GetItem_AsDouble(o1, 1);
        double o1_z = sequence_GetItem_AsDouble(o1, 2);
        return Vector3d_NEW(o1_x + ((Vector3d*)o2)->x, 
                            o1_y + ((Vector3d*)o2)->y, 
                            o1_z + ((Vector3d*)o2)->z);
    }
}

static PyObject *
Vector3d_inplace_add(Vector3d *self, PyObject *other)
{
    if (!checkVector3dCompatible(other)) {
        return Py_NotImplemented;
    }

    self->x += sequence_GetItem_AsDouble(other, 0);
    self->y += sequence_GetItem_AsDouble(other, 1);
    self->z += sequence_GetItem_AsDouble(other, 2);
    Py_INCREF(self);
    return (PyObject*)self;
}


static PyObject *
Vector3d_sub(PyObject *o1, PyObject *o2)
{
    if (!checkVector3dCompatible(o1))
        return Py_NotImplemented;
    if (!checkVector3dCompatible(o2))
        return Py_NotImplemented;

    if (Vector3d_Check(o1)) {
        if (Vector3d_Check(o2)) {
            return Vector3d_NEW(((Vector3d*)o1)->x - ((Vector3d*)o2)->x, 
                                ((Vector3d*)o1)->y - ((Vector3d*)o2)->y, 
                                ((Vector3d*)o1)->z - ((Vector3d*)o2)->z);
        } 
        else {
            double o2_x = sequence_GetItem_AsDouble(o2, 0);
            double o2_y = sequence_GetItem_AsDouble(o2, 1);
            double o2_z = sequence_GetItem_AsDouble(o2, 2);
            return Vector3d_NEW(((Vector3d*)o1)->x - o2_x, 
                                ((Vector3d*)o1)->y - o2_y, 
                                ((Vector3d*)o1)->z - o2_z);
        }
    } 
    else {
        double o1_x = sequence_GetItem_AsDouble(o1, 0);
        double o1_y = sequence_GetItem_AsDouble(o1, 1);
        double o1_z = sequence_GetItem_AsDouble(o1, 2);
        return Vector3d_NEW(o1_x - ((Vector3d*)o2)->x, 
                            o1_y - ((Vector3d*)o2)->y, 
                            o1_z - ((Vector3d*)o2)->z);
    }
}

static PyObject *
Vector3d_inplace_sub(Vector3d *self, PyObject *other)
{
    if (!checkVector3dCompatible(other))
        return Py_NotImplemented;

    self->x -= sequence_GetItem_AsDouble(other, 0);
    self->y -= sequence_GetItem_AsDouble(other, 1);
    self->z -= sequence_GetItem_AsDouble(other, 2);
    Py_INCREF(self);
    return (PyObject*)self;
}

static PyObject *
Vector3d_mul(PyObject *o1, PyObject *o2)
{
    double tmp;
    if (Vector3d_Check(o1)) {
        if (Vector3d_Check(o2)) {
            return PyFloat_FromDouble((((Vector3d*)o1)->x * ((Vector3d*)o2)->x) +
                                      (((Vector3d*)o1)->y * ((Vector3d*)o2)->y) +
                                      (((Vector3d*)o1)->z * ((Vector3d*)o2)->z));
        } 
        else if (checkVector3dCompatible(o2)) {
            return PyFloat_FromDouble((((Vector3d*)o1)->x * sequence_GetItem_AsDouble(o2, 0)) +
                                      (((Vector3d*)o1)->y * sequence_GetItem_AsDouble(o2, 1)) +
                                      (((Vector3d*)o1)->z * sequence_GetItem_AsDouble(o2, 2)));
        } 
        else if (checkRealNumber(o2)) {
            tmp = PyFloat_AsDouble(o2);
            return Vector3d_NEW(((Vector3d*)o1)->x * tmp, ((Vector3d*)o1)->y * tmp, ((Vector3d*)o1)->z * tmp);
        }
    } 
    else {
        if (checkVector3dCompatible(o1)) {
            return PyFloat_FromDouble((sequence_GetItem_AsDouble(o1, 0) * ((Vector3d*)o2)->x) +
                                      (sequence_GetItem_AsDouble(o1, 1) * ((Vector3d*)o2)->y) +
                                      (sequence_GetItem_AsDouble(o1, 2) * ((Vector3d*)o2)->z));
        } 
        else if (checkRealNumber(o1)) {
            tmp = PyFloat_AsDouble(o1);
            return Vector3d_NEW(tmp * ((Vector3d*)o2)->x, tmp * ((Vector3d*)o2)->y, tmp * ((Vector3d*)o2)->z);
        }
    }
    return Py_NotImplemented;
}

static PyObject *
Vector3d_inplace_mul(Vector3d *self, PyObject *other)
{
    if (PyFloat_Check(other)) {
        double tmp = PyFloat_AsDouble(other);
        self->x *= tmp;
        self->y *= tmp;
        self->z *= tmp;
        Py_INCREF(self);
        return (PyObject*)self;
    } 
    else if (checkVector3dCompatible(other)) {
        PyErr_SetString(PyExc_TypeError, "cannot perform scalar product inplace.");
        return NULL;
    } 
    else {
        return Py_NotImplemented;
    }
}

static PyObject *
Vector3d_div(Vector3d *self, PyObject *other)
{
    if (Vector3d_Check(self) && checkRealNumber(other)) {
        double tmp = 1. / PyFloat_AsDouble(other);
        return Vector3d_NEW(self->x * tmp, self->y * tmp, self->z * tmp);
    }
    else {
        return Py_NotImplemented;
    }
}

static PyObject *
Vector3d_inplace_div(Vector3d *self, PyObject *other)
{
    if (checkRealNumber(other)) {
        double tmp = 1. / PyFloat_AsDouble(other);
        self->x *= tmp;
        self->y *= tmp;
        self->z *= tmp;
        Py_INCREF(self);
        return (PyObject*)self;
    }
    else {
        return Py_NotImplemented;
    }
}

static PyObject *
Vector3d_floor_div(Vector3d *self, PyObject *other)
{
    if (Vector3d_Check(self) && checkRealNumber(other)) {
        double tmp = 1. / PyFloat_AsDouble(other);
        return Vector3d_NEW(floor(self->x * tmp), floor(self->y * tmp), floor(self->z * tmp));
    }
    else {
        return Py_NotImplemented;
    }
}

static PyObject *
Vector3d_inplace_floor_div(Vector3d *self, PyObject *other)
{
    if (checkRealNumber(other)) {
        double tmp = 1. / PyFloat_AsDouble(other);
        self->x = floor(self->x * tmp);
        self->y = floor(self->y * tmp);;
        self->z = floor(self->z * tmp);
        Py_INCREF(self);
        return (PyObject*)self;
    }
    else {
        return Py_NotImplemented;
    }
}

static PyObject *
Vector3d_neg(Vector3d *self)
{
    return Vector3d_NEW(-self->x, -self->y, -self->z);
}

static PyObject *
Vector3d_pos(Vector3d *self)
{
    return Vector3d_NEW(self->x, self->y, self->z);
}

static int
Vector3d_nonzero(Vector3d *self)
{
    if ((fabs(self->x) > self->epsilon) || 
        (fabs(self->y) > self->epsilon) || 
        (fabs(self->z) > self->epsilon)) {
        return 1;
    }
    else {
        return 0;
    }
}

PyNumberMethods Vector3d_as_number = {
    (binaryfunc)Vector3d_add,              /* binaryfunc nb_add;         /* __add__ */
    (binaryfunc)Vector3d_sub,              /* binaryfunc nb_subtract;    /* __sub__ */
    (binaryfunc)Vector3d_mul,              /* binaryfunc nb_multiply;    /* __mul__ */
    (binaryfunc)Vector3d_div,              /* binaryfunc nb_divide;      /* __div__ */
    0,                                     /* binaryfunc nb_remainder;   /* __mod__ */
    0,                                     /* binaryfunc nb_divmod;      /* __divmod__ */
    0,                                     /* ternaryfunc nb_power;      /* __pow__ */
    (unaryfunc)Vector3d_neg,               /* unaryfunc nb_negative;     /* __neg__ */
    (unaryfunc)Vector3d_pos,               /* unaryfunc nb_positive;     /* __pos__ */
    0,                                     /* unaryfunc nb_absolute;     /* __abs__ */
    (inquiry)Vector3d_nonzero,             /* inquiry nb_nonzero;        /* __nonzero__ */
    0,                                     /* unaryfunc nb_invert;       /* __invert__ */
    0,                                     /* binaryfunc nb_lshift;      /* __lshift__ */
    0,                                     /* binaryfunc nb_rshift;      /* __rshift__ */
    0,                                     /* binaryfunc nb_and;         /* __and__ */
    0,                                     /* binaryfunc nb_xor;         /* __xor__ */
    0,                                     /* binaryfunc nb_or;          /* __or__ */
    0,                                     /* coercion nb_coerce;        /* __coerce__ */
    0,                                     /* unaryfunc nb_int;          /* __int__ */
    0,                                     /* unaryfunc nb_long;         /* __long__ */
    0,                                     /* unaryfunc nb_float;        /* __float__ */
    0,                                     /* unaryfunc nb_oct;          /* __oct__ */
    0,                                     /* unaryfunc nb_hex;          /* __hex__ */

    /* Added in release 2.0 */
    (binaryfunc)Vector3d_inplace_add,      /* binaryfunc nb_inplace_add; /* __iadd__ */
    (binaryfunc)Vector3d_inplace_sub,      /* binaryfunc nb_inplace_subtract; /* __isub__ */
    (binaryfunc)Vector3d_inplace_mul,      /* binaryfunc nb_inplace_multiply; /* __imul__ */
    (binaryfunc)Vector3d_inplace_div,      /* binaryfunc nb_inplace_divide; /* __idiv__ */
    0,                                     /* binaryfunc nb_inplace_remainder; /* __imod__ */
    0,                                     /* ternaryfunc nb_inplace_power; /* __pow__ */
    0,                                     /* binaryfunc nb_inplace_lshift; /* __ilshift__ */
    0,                                     /* binaryfunc nb_inplace_rshift; /* __irshift__ */
    0,                                     /* binaryfunc nb_inplace_and; /* __iand__ */
    0,                                     /* binaryfunc nb_inplace_xor; /* __ixor__ */
    0,                                     /* binaryfunc nb_inplace_or;  /* __ior__ */

    /* Added in release 2.2 */
    (binaryfunc)Vector3d_floor_div,        /* binaryfunc nb_floor_divide; /* __floor__ */
    (binaryfunc)Vector3d_div,              /* binaryfunc nb_true_divide; /* __truediv__ */
    (binaryfunc)Vector3d_inplace_floor_div, /* binaryfunc nb_inplace_floor_divide; /* __ifloor__ */
    (binaryfunc)Vector3d_inplace_div,      /* binaryfunc nb_inplace_true_divide; /* __itruediv__ */

    /* Added in release 2.5 */
    0,                                     /* unaryfunc nb_index; /* __index__ */
};




static PyObject *
Vector3d_len(PyObject *self)
{
    return PyLong_FromLong(3);
}

static PyObject *
Vector3d_GetItem(Vector3d *self, Py_ssize_t index)
{
    switch (index) {
    case 0:
        return PyFloat_FromDouble(self->x);
        break;
    case 1:
        return PyFloat_FromDouble(self->y);
        break;
    case 2:
        return PyFloat_FromDouble(self->z);
        break;
    default:
        PyErr_SetString(PyExc_TypeError, "subscript out of range.");
        return NULL;
        break;
    }
}

static int
Vector3d_SetItem(Vector3d *self, Py_ssize_t index, PyObject *value)
{
    if (!checkRealNumber(value)) {
        PyErr_SetString(PyExc_TypeError, "cannot assign a non float.");
        return -1;
    }
    switch (index) {
    case 0:
        self->x = PyFloat_AsDouble(value);
        break;
    case 1:
        self->y = PyFloat_AsDouble(value);
        break;
    case 2:
        self->z = PyFloat_AsDouble(value);
        break;
    default:
        PyErr_SetString(PyExc_TypeError, "subscript out of range.");
        return -1;
        break;
    }
    Py_DECREF(value);
    return 0;
}

PySequenceMethods Vector3d_as_sequence = {
    (lenfunc)Vector3d_len,       /* lenfunc sq_length;             /* __len__ */
    0,                  /* binaryfunc sq_concat;          /* __add__ */
    0,                  /* intargfunc sq_repeat;          /* __mul__ */
    (ssizeargfunc)Vector3d_GetItem,   /* ssizeargfunc sq_item;            /* __getitem__ */
    0,                  /* intintargfunc sq_slice;        /* __getslice__ */
    (ssizeobjargproc)Vector3d_SetItem,   /* intobjargproc sq_ass_item;     /* __setitem__ */
    0,                  /* intintobjargproc sq_ass_slice; /* __setslice__ */
};




/**********************************
 * Properties
 **********************************/

static PyObject *
Vector3d_getLength(Vector3d *self, void *closure)
{
    double length = sqrt(self->x * self->x +
                         self->y * self->y +
                         self->z * self->z);
    return PyFloat_FromDouble(length);
}

static int
Vector3d_setLength(Vector3d *self, PyObject *newLength, void *closure)
{
    double oldLength, factor;
    if (newLength == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the length attribute");
        return -1;
    }

    if (! PyNumber_Check(newLength)) {
        PyErr_SetString(PyExc_TypeError,
                        "The length attribute value must be a number");
        return -1;
    }

    oldLength = sqrt(self->x * self->x +
                     self->y * self->y +
                     self->z * self->z);
    factor = PyFloat_AsDouble(newLength) / oldLength;
    self->x *= factor;
    self->y *= factor;
    self->z *= factor;
    return 0;
}

static PyObject *
Vector3d_getLengthSquared(Vector3d *self, void *closure)
{
    double length = (self->x * self->x +
                     self->y * self->y +
                     self->z * self->z);
    return PyFloat_FromDouble(length);
}

static int
Vector3d_setLengthSquared(Vector3d *self, PyObject *newLengthSquared, void *closure)
{
    double oldLengthSquared, factor;
    if (newLengthSquared == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the lengthSquared attribute");
        return -1;
    }

    if (!checkRealNumber(newLengthSquared)) {
        PyErr_SetString(PyExc_TypeError,
                        "The lengthSquared attribute value must be a real number");
        return -1;
    }

    oldLengthSquared = (self->x * self->x +
                               self->y * self->y +
                               self->z * self->z);
    factor = PyFloat_AsDouble(newLengthSquared) / oldLengthSquared;
    self->x *= factor;
    self->y *= factor;
    self->z *= factor;
    return 0;
}

static PyObject *
Vector3d_getTheta(Vector3d *self, void *closure)
{
    double theta = atan2(sqrt((self->x * self->x) + (self->y * self->y)), self->z);
    return PyFloat_FromDouble(theta);
}

static int
Vector3d_setTheta(Vector3d *self, PyObject *newTheta, void *closure)
{
    double theta, sinTheta, phi, r;
    if (newTheta == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the theta attribute");
        return -1;
    }

    if (!checkRealNumber(newTheta)) {
        PyErr_SetString(PyExc_TypeError,
                        "The theta attribute value must be a real number");
        return -1;
    }

    theta = PyFloat_AsDouble(newTheta);
    sinTheta = sin(theta);
    phi = atan2(self->y, self->x);
    r = sqrt((self->x * self->x) + (self->y * self->y) + (self->z * self->z));
    self->x = r * sinTheta * cos(phi);
    self->y = r * sinTheta * sin(phi);
    self->z = r * cos(theta);
    return 0;
}

static PyObject *
Vector3d_getPhi(Vector3d *self, void *closure)
{
    double phi = atan2(self->y, self->x);
    return PyFloat_FromDouble(phi);
}

static int
Vector3d_setPhi(Vector3d *self, PyObject *newPhi, void *closure)
{
    double theta, sinTheta, phi, r;
    if (newPhi == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the phi attribute");
        return -1;
    }

    if (!checkRealNumber(newPhi)) {
        PyErr_SetString(PyExc_TypeError,
                        "The phi attribute value must be a real number");
        return -1;
    }

    theta = atan2(sqrt((self->x * self->x) + (self->y * self->y)), self->z);
    sinTheta = sin(theta);
    phi = PyFloat_AsDouble(newPhi);
    r = sqrt((self->x * self->x) + (self->y * self->y) + (self->z * self->z));
    self->x = r * sinTheta * cos(phi);
    self->y = r * sinTheta * sin(phi);
    self->z = r * cos(theta);
    return 0;
}


static PyGetSetDef Vector3d_getseters[] = {
    {"length",
     (getter)Vector3d_getLength, (setter)Vector3d_setLength,
     "Length of the vector",
     NULL},
    {"lengthSquared",
     (getter)Vector3d_getLengthSquared, (setter)Vector3d_setLengthSquared,
     "Length of the vector squared",
     NULL},
    {"theta",
     (getter)Vector3d_getTheta, (setter)Vector3d_setTheta,
     "The angle Theta in a spherical coordinate system.",
     NULL},
    {"phi",
     (getter)Vector3d_getPhi, (setter)Vector3d_setPhi,
     "The angle Phi in a spherical coordinate system.",
     NULL},
    {NULL}  /* Sentinel */
};



/**********************************
 * Methods
 **********************************/

static PyObject *
Vector3d_rotated(Vector3d *self, PyObject *args)
{
    double angle, sinValue, cosValue, cos2;
    Vector3d *axis;
    if (!PyArg_ParseTuple(args, "dO!", &angle, &Vector3d_Type, &axis)) {
        return NULL;
    }
    /* normalize the axis */
    if ((axis->x * axis->x) + (axis->y * axis->y) + (axis->z * axis->z) - 1 > axis->epsilon) {
        double normalizationFactor = 1. / sqrt((axis->x * axis->x) + 
                                               (axis->y * axis->y) + 
                                               (axis->z * axis->z));
        axis->x *= normalizationFactor;
        axis->y *= normalizationFactor;
        axis->z *= normalizationFactor;
    }
    sinValue = sin(angle);
    cosValue = cos(angle);
    cos2 = 1 - cosValue;
    return Vector3d_NEW((self->x * (cosValue + axis->x * axis->x * cos2) +
                         self->y * (axis->x * axis->y * cos2 + axis->z * sinValue) +
                         self->z * (axis->x * axis->z * cos2 - axis->y * sinValue)),
                        (self->x * (axis->x * axis->y * cos2 - axis->z * sinValue) +
                         self->y * (cosValue + axis->y * axis->y * cos2) +
                         self->z * (axis->y * axis->z * cos2 - axis->x * sinValue)),
                        (self->x * (axis->x * axis->z * cos2 + axis->y * sinValue) +
                         self->y * (axis->y * axis->z * cos2 - axis->x * sinValue) +
                         self->z * (cosValue + axis->z * axis->z * cos2)));
}

static PyObject *
Vector3d_rotate(Vector3d *self, PyObject *args)
{
    double angle, sinValue, cosValue, cos2;
    Vector3d *axis;
    if (!PyArg_ParseTuple(args, "dO!", &angle, &Vector3d_Type, &axis)) {
        return NULL;
    }
    /* normalize the axis */
    if ((axis->x * axis->x) + (axis->y * axis->y) + (axis->z * axis->z) - 1 > axis->epsilon) {
        double normalizationFactor = 1. / sqrt((axis->x * axis->x) + 
                                               (axis->y * axis->y) + 
                                               (axis->z * axis->z));
        axis->x *= normalizationFactor;
        axis->y *= normalizationFactor;
        axis->z *= normalizationFactor;
    }
    sinValue = sin(angle);
    cosValue = cos(angle);
    cos2 = 1 - cosValue;
    self->x = (self->x * (cosValue + axis->x * axis->x * cos2) +
               self->y * (axis->x * axis->y * cos2 + axis->z * sinValue) +
               self->z * (axis->x * axis->z * cos2 - axis->y * sinValue));
    self->y = (self->x * (axis->x * axis->y * cos2 - axis->z * sinValue) +
               self->y * (cosValue + axis->y * axis->y * cos2) +
               self->z * (axis->y * axis->z * cos2 - axis->x * sinValue));
    self->z = (self->x * (axis->x * axis->z * cos2 + axis->y * sinValue) +
               self->y * (axis->y * axis->z * cos2 - axis->x * sinValue) +
               self->z * (cosValue + axis->z * axis->z * cos2));
    Py_RETURN_NONE;
}

static PyObject *
Vector3d_rotatedX(Vector3d *self, PyObject *angleObject)
{
    double angle, sinValue, cosValue;
    if (!checkRealNumber(angleObject)) {
        PyErr_SetString(PyExc_TypeError, "angle must be a real number");
        return NULL;
    }
    angle = PyFloat_AsDouble(angleObject);
    sinValue = sin(angle);
    cosValue = cos(angle);
    return Vector3d_NEW(self->x,
                        self->y * cosValue + self->z * sinValue,
                        -self->y * sinValue + self->z * cosValue);
}

static PyObject *
Vector3d_rotateX(Vector3d *self, PyObject *angleObject)
{
    double angle, sinValue, cosValue, y, z;
    if (!checkRealNumber(angleObject)) {
        PyErr_SetString(PyExc_TypeError, "angle must be a real number");
        return NULL;
    }
    angle = PyFloat_AsDouble(angleObject);
    sinValue = sin(angle);
    cosValue = cos(angle);
    y = self->y;
    z = self->z;
    
    self->y = y * cosValue + z * sinValue;
    self->z = -y * sinValue + z * cosValue;
    Py_RETURN_NONE;
}

static PyObject *
Vector3d_rotatedY(Vector3d *self, PyObject *angleObject)
{
    double angle, sinValue, cosValue;
    if (!checkRealNumber(angleObject)) {
        PyErr_SetString(PyExc_TypeError, "angle must be a real number");
        return NULL;
    }

    angle = PyFloat_AsDouble(angleObject);
    sinValue = sin(angle);
    cosValue = cos(angle);
    return Vector3d_NEW(self->x * cosValue + self->z * sinValue,
                        self->y,
                        -self->x * sinValue + self->z * cosValue);

}

static PyObject *
Vector3d_rotateY(Vector3d *self, PyObject *angleObject)
{
    double angle, sinValue, cosValue, x, z;
    if (!checkRealNumber(angleObject)) {
        PyErr_SetString(PyExc_TypeError, "angle must be a real number");
        return NULL;
    }

    angle = PyFloat_AsDouble(angleObject);
    sinValue = sin(angle);
    cosValue = cos(angle);
    x = self->x;
    z = self->z;
    
    self->x = x * cosValue + z * sinValue;
    self->z = -x * sinValue + z * cosValue;
    Py_RETURN_NONE;
}

static PyObject *
Vector3d_rotatedZ(Vector3d *self, PyObject *angleObject)
{
    double angle, sinValue, cosValue;
    if (!checkRealNumber(angleObject)) {
        PyErr_SetString(PyExc_TypeError, "angle must be a real number");
        return NULL;
    }

    angle = PyFloat_AsDouble(angleObject);
    sinValue = sin(angle);
    cosValue = cos(angle);
    return Vector3d_NEW(self->x * cosValue + self->y * sinValue,
                        -self->x * sinValue + self->y * cosValue,
                        self->z);
}

static PyObject *
Vector3d_rotateZ(Vector3d *self, PyObject *angleObject)
{
    double angle, sinValue, cosValue, x, y;
    if (!checkRealNumber(angleObject)) {
        PyErr_SetString(PyExc_TypeError, "angle must be a real number");
        return NULL;
    }

    angle = PyFloat_AsDouble(angleObject);
    sinValue = sin(angle);
    cosValue = cos(angle);
    x = self->x;
    y = self->y;
    
    self->x = x * cosValue + y * sinValue;
    self->y = -x * sinValue + y * cosValue;
    Py_RETURN_NONE;
}

static PyObject *
Vector3d_normalize(Vector3d *self)
{
    double factor = 1./sqrt((self->x * self->x) + (self->y * self->y) + (self->z * self->z));
    self->x *= factor;
    self->y *= factor;
    self->z *= factor;
    Py_RETURN_NONE;
}

static PyObject *
Vector3d_normalized(Vector3d *self)
{
    double factor = 1./sqrt((self->x * self->x) + (self->y * self->y) + (self->z * self->z));
    return Vector3d_NEW(self->x * factor, self->y * factor, self->z * factor);
}

static PyObject *
Vector3d_isNormalized(Vector3d *self)
{
    if (fabs((self->x * self->x) + (self->y * self->y) + (self->z * self->z) - 1.) < self->epsilon) {
        Py_RETURN_TRUE;
    } 
    else {
        Py_RETURN_FALSE;
    }
}

static PyObject *
Vector3d_cross(Vector3d *self, PyObject *other)
{
    double other_x, other_y, other_z;
    if (!checkVector3dCompatible(other)) {
        PyErr_SetString(PyExc_TypeError, "cannot calculate cross Product");
        return NULL;
    }

    if (Vector3d_Check(other)) {
        other_x = ((Vector3d*)other)->x;
        other_y = ((Vector3d*)other)->y;
        other_z = ((Vector3d*)other)->z;
    }
    else {
        other_x = sequence_GetItem_AsDouble(other, 0);
        other_y = sequence_GetItem_AsDouble(other, 1);
        other_z = sequence_GetItem_AsDouble(other, 2);
    }
    return Vector3d_NEW((self->y * other_z) - (self->z * other_y),
                        (self->z * other_x) - (self->x * other_z),
                        (self->x * other_y) - (self->y * other_x));
}

static PyObject *
Vector3d_dot(Vector3d *self, PyObject *other)
{
    double other_x, other_y, other_z;
    if (!checkVector3dCompatible(other)) {
        PyErr_SetString(PyExc_TypeError, "cannot calculate dot Product");
        return NULL;
    }

    if (Vector3d_Check(other)) {
        other_x = ((Vector3d*)other)->x;
        other_y = ((Vector3d*)other)->y;
        other_z = ((Vector3d*)other)->z;
    } 
    else {
        other_x = sequence_GetItem_AsDouble(other, 0);
        other_y = sequence_GetItem_AsDouble(other, 1);
        other_z = sequence_GetItem_AsDouble(other, 2);
    }
    return PyFloat_FromDouble((self->x * other_x) + 
                              (self->y * other_y) + 
                              (self->z * other_z));
}


static PyObject *
Vector3d_angleTo(Vector3d *self, PyObject *other)
{
    double other_x, other_y, other_z, normSquared1, normSquared2, angle;
    if (!checkVector3dCompatible(other)) {
        PyErr_SetString(PyExc_TypeError, "cannot determine angle to this argument");
        return NULL;
    }
    
    if (Vector3d_Check(other)) {
        other_x = ((Vector3d*)other)->x;
        other_y = ((Vector3d*)other)->y;
        other_z = ((Vector3d*)other)->z;
    }
    else {
        other_x = sequence_GetItem_AsDouble(other, 0);
        other_y = sequence_GetItem_AsDouble(other, 1);
        other_z = sequence_GetItem_AsDouble(other, 2);
    }
    normSquared1 = ((self->x * self->x) + 
                    (self->y * self->y) +
                    (self->z * self->z));
    normSquared2 = ((other_x * other_x) +
                    (other_y * other_y) +
                    (other_z * other_z));
    angle = acos((self->x * other_x + self->y * other_y + self->z * other_z) / 
                 (sqrt(normSquared1 * normSquared2)));
    return PyFloat_FromDouble(angle);
}


static PyMethodDef Vector3d_methods[] = {
    {"rotate", (PyCFunction)Vector3d_rotate, METH_VARARGS,
     "rotates the vector around the given axis by the amount given by angle."
    },
    {"rotated", (PyCFunction)Vector3d_rotated, METH_VARARGS,
     "returns a vector rotated around the given axis by the amount given by angle."
    },
    {"rotateX", (PyCFunction)Vector3d_rotateX, METH_O,
     "rotates the vector around the X-Axis by the amount given by angle."
    },
    {"rotatedX", (PyCFunction)Vector3d_rotatedX, METH_O,
     "returns a vector rotated around the X-Axis by the amount given by angle."
    },
    {"rotateY", (PyCFunction)Vector3d_rotateY, METH_O,
     "rotates the vector around the Y-Axis by the amount given by angle."
    },
    {"rotatedY", (PyCFunction)Vector3d_rotatedY, METH_O,
     "returns a vector rotated around the Y-Axis by the amount given by angle."
    },
    {"rotateZ", (PyCFunction)Vector3d_rotateZ, METH_O,
     "rotates the vector around the Z-Axis by the amount given by angle."
    },
    {"rotatedZ", (PyCFunction)Vector3d_rotatedZ, METH_O,
     "returns a vector rotated around the Z-Axis by the amount given by angle."
    },
    {"normalized", (PyCFunction)Vector3d_normalized, METH_NOARGS,
     "returns a vector that has length == 1 and the same direction as self."
    },
    {"normalize", (PyCFunction)Vector3d_normalize, METH_NOARGS,
     "Normalizes the vector so that it has length == 1."
    },
    {"isNormalized", (PyCFunction)Vector3d_isNormalized, METH_NOARGS,
     "returns True if the vector has length == 1. otherwise it returns False."
    },
    {"cross", (PyCFunction)Vector3d_cross, METH_O,
     "calculates the cross product."
    },
    {"dot", (PyCFunction)Vector3d_dot, METH_O,
     "calculates the dot product."
    },
    {"angleTo", (PyCFunction)Vector3d_angleTo, METH_O,
     "returns the angle between self and the given vector."
    },
    {NULL}  /* Sentinel */
};


/**********************************
 * Callback routines
 **********************************/

static PyObject *
Vector3d_repr(Vector3d *self)
{
    const int BUFFER_SIZE = 255;
    wchar_t buffer[BUFFER_SIZE];
    int strLength = swprintf(buffer, BUFFER_SIZE, L"<Vector3d(%lg, %lg, %lg)>", self->x, self->y, self->z);
    return PyUnicode_FromWideChar(buffer, strLength);
}

static PyObject *
Vector3d_str(Vector3d *self)
{
    const int BUFFER_SIZE = 255;
    wchar_t buffer[BUFFER_SIZE];
    int strLength = swprintf(buffer, BUFFER_SIZE, L"(%lg, %lg, %lg)", self->x, self->y, self->z);
    return PyUnicode_FromWideChar(buffer, strLength);
}

static PyObject *
Vector3d_richcompare(Vector3d *self, PyObject *other, int op)
{
    double other_x, other_y, other_z;
    if (Vector3d_Check(other)) {
        other_x = ((Vector3d*)other)->x;
        other_y = ((Vector3d*)other)->y;
        other_z = ((Vector3d*)other)->z;
    } 
    else if (checkVector3dCompatible(other)) {
        other_x = sequence_GetItem_AsDouble(other, 0);
        other_y = sequence_GetItem_AsDouble(other, 1);
        other_z = sequence_GetItem_AsDouble(other, 2);
    }
    else {
        /* if other is not Vector3d or sequence they are not equal in any case */
        if (op == Py_EQ)
            Py_RETURN_FALSE;
        else if (op == Py_NE)
            Py_RETURN_TRUE;
        else
            return Py_NotImplemented;
    }

    switch(op)
    {
    case Py_EQ:
        if ((fabs(self->x - other_x) < self->epsilon) &&
            (fabs(self->y - other_y) < self->epsilon) &&
            (fabs(self->z - other_z) < self->epsilon))
            Py_RETURN_TRUE;
        else
            Py_RETURN_FALSE;
        break;
    case Py_NE:
        if ((fabs(self->x - other_x) >= self->epsilon) ||
            (fabs(self->y - other_y) >= self->epsilon) ||
            (fabs(self->z - other_z) >= self->epsilon))
            Py_RETURN_TRUE;
        else
            Py_RETURN_FALSE;
        break;
    default:
        return Py_NotImplemented;
    }
}


/********************************
 * Type definition
 ********************************/

static PyTypeObject Vector3d_Type = {
    PyObject_HEAD_INIT(NULL)
    0,                         /*ob_size*/
    "vector.Vector3d",         /*tp_name*/
    sizeof(Vector3d),          /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    /* Methods to implement standard operations */
    (destructor)Vector3d_dealloc, /*tp_dealloc*/
    0,                         /*tp_print*/
    0,                         /*tp_getattr*/
    0,                         /*tp_setattr*/
    0,                         /*tp_compare*/
    (reprfunc)Vector3d_repr,   /*tp_repr*/
    /* Method suites for standard classes */
    &Vector3d_as_number,       /*tp_as_number*/
    &Vector3d_as_sequence,     /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    /* More standard operations (here for binary compatibility) */
    0,                         /*tp_hash */
    0,                         /*tp_call*/
    (reprfunc)Vector3d_str,    /*tp_str*/
    PyObject_GenericGetAttr,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    /* Functions to access object as input/output buffer */
    0,                         /*tp_as_buffer*/
    /* Flags to define presence of optional/expanded features */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES | Py_TPFLAGS_HAVE_INPLACEOPS, /*tp_flags*/
    /* Documentation string */
    "3D Vector",               /* tp_doc */

    /* Assigned meaning in release 2.0 */
    /* call function for all accessible objects */
    0,                         /* tp_traverse */
    /* delete references to contained objects */
    0,                         /* tp_clear */

    /* Assigned meaning in release 2.1 */
    /* rich comparisons */
    (richcmpfunc)Vector3d_richcompare, /* tp_richcompare */
    /* weak reference enabler */
    0,                         /* tp_weaklistoffset */

    /* Added in release 2.2 */
    /* Iterators */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    /* Attribute descriptor and subclassing stuff */
    Vector3d_methods,          /* tp_methods */
    Vector3d_members,          /* tp_members */
    Vector3d_getseters,        /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)Vector3d_init,   /* tp_init */
    0,                         /* tp_alloc */
    (newfunc)Vector3d_new,     /* tp_new */
    0,                         /* tp_free */
    0,                         /* tp_is_gc */
    0,                         /* tp_bases */
    0,                         /* tp_mro */
    0,                         /* tp_cache */
    0,                         /* tp_subclasses */
    0,                         /* tp_weaklist */
};





/****************************
 * Module functions
 ****************************/

static PyMethodDef vector_methods[] = {
    {NULL}  /* Sentinel */
};


/****************************
 * Module init function
 ****************************/

#ifndef PyMODINIT_FUNC    /* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
initvector(void)
{
    PyObject* m;

    if (PyType_Ready(&Vector3d_Type) < 0)
        return;

    m = Py_InitModule3("vector", vector_methods,
                       "Vector module with a 3D Vector type.");

    Py_INCREF(&Vector3d_Type);
    PyModule_AddObject(m, "Vector3d", (PyObject *)&Vector3d_Type);
}

"""
Written by Lorenz Quack.
No right reserved.
Some ideas for unittesting were taken from here:
    http://www.pygame.org/wiki/2DVectorClass

"""
# HACK!!!
import sys
sys.path.append("./build/lib.linux-x86_64-2.6/")

from vector import Vector3d
import unittest
import pickle
import math


class UnitTestVector3D(unittest.TestCase):

    def setUp(self):
        pass

    def testCreationAndAccess(self):
        v = Vector3d(-111, 222, 333.3)
        self.assert_(v.x == -111 and v.y == 222 and v.z == 333.3)
        v.x = 444
        v.y = 555.5
        v.z = -666
        self.assert_(v.x == 444 and v.y == 555.5 and v.z == -666)
        v[0] = 777.7
        v[1] = -888
        v[2] = 999
        self.assert_(v[0] == 777.7 and v[1] == -888 and v[2] == 999)
        v = Vector3d((-111, 222, 333.3))
        self.assert_(v.x == -111 and v.y == 222 and v.z == 333.3)
        v.z = 333.3
        v2 = Vector3d(v)
        self.assert_(v2.x == -111 and v2.y == 222 and v2.z == 333.3)
        v2.x = 111
        self.assert_(v.x == -111 and v2.x == 111)
        v = Vector3d()
        self.assert_(v.x == 0 and v.y == 0. and v.z == -0)

    def testMath(self):
        v1 = Vector3d(111, 222, 333)
        v2 = Vector3d(333, 555, 666)
        self.assertEqual(v1 + v2, Vector3d(444, 777, 999))
        self.assert_(v1 + [-11, 78, 67] == Vector3d(100, 300, 400))
        self.assert_((-11, 78, 67) + v1 == Vector3d(100, 300, 400))
        self.assert_(v1 - v2 == [-222, -333, -333])
        self.assert_(v1 - (11, 2, -7) == [100, 220, 340])
        self.assert_([11, 2, -7] - v1 == (-100, -220, -340))
        self.assert_(v1 * 3 == (333, 666, 999))
        self.assert_(2 * v1 == (222, 444, 666))
        self.assert_(v1 / 2.0 == Vector3d(55.5, 111, 166.5))
        self.assert_(v1 // 2 == (55, 111, 166))
        self.assert_(v1 * v2 == 381951)
        self.assert_(v1 * (333, 555, 666) == 381951)
        self.assert_([111, 222, 333] * v2 == 381951)
        self.assert_(v1.dot(v2) == 381951)
        self.assert_(v1.dot((333, 555, 666)) == 381951)
        self.assert_(v1.dot(v2) == v2.dot(v1))
        self.assert_(v1.dot(v1) == v1 * v1)
        self.assert_(v1.dot(v1) == v1.lengthSquared)
        self.assert_(v1.cross(v1) == Vector3d())
        self.assert_(v1.cross(v2) == Vector3d(-36963, 36963, -12321))
        self.assert_(v2.cross(v1) == -v1.cross(v2))

    def testUnary(self):
        v = Vector3d(-111, 222.2, 333)
        v = -v
        self.assert_(v == [111, -222.2, -333])
        v = +v
        self.assert_(v == (111, -222.2, -333))

    def testLength(self):
        v = Vector3d(3, 4, 0)
        self.assert_(v.isNormalized() == False)
        self.assert_(v.length == 5)
        self.assert_(v.lengthSquared == 25)
        v = Vector3d(1, 2, 3)
        self.assertAlmostEquals(v.length, 3.74165738677394138558)
        self.assertEquals(v.lengthSquared, 14)
        v2 = v.normalized()
        self.assertAlmostEquals(v2.length, 1)
        v.normalize()
        self.assertEquals(v2.length, v.length)
        self.assert_(v.normalize() == None)
        self.assert_(v.isNormalized() == True)

    def testComparison(self):
        int_vec = Vector3d(3, -2, 1)
        flt_vec = Vector3d(3.0, -2.0, 1.)
        zero_vec = Vector3d(0, 0, 0)
        self.assert_(int_vec == flt_vec)
        self.assert_(int_vec != zero_vec)
        self.assert_((flt_vec == zero_vec) == False)
        self.assert_((flt_vec != int_vec) == False)
        self.assert_(int_vec == (3, -2, 1))
        self.assert_(int_vec != [0, 0, 0])
        self.assert_(int_vec != 5)
        self.assert_(int_vec != [3, -2, -5, 3])
        self.assert_(flt_vec)
        self.assert_(not zero_vec)
        self.assert_(bool(flt_vec) == True)
        self.assert_(bool(zero_vec) == False)

    def testInplace(self):
        inplace_vec = Vector3d(50, 130.1, -82)
        inplace_ref = inplace_vec
        inplace_src = Vector3d(inplace_vec)
        inplace_vec *= .5
        inplace_vec /= 3
        inplace_vec += Vector3d(-1, -1, 1)
        alternate = (inplace_src * .5) / 3 + [-1, -1, 1]
        self.assertEquals(inplace_vec, alternate)
        inplace_vec //= 2
        alternate = ((inplace_src * .5) / 3 + [-1, -1, 1])//2
        self.assertEquals(inplace_vec, alternate)
        self.assertEquals(inplace_vec, inplace_ref)

    def testRotation(self):
        # TODO: write this
        self.assert_("write this test" == None)

    def testAngles(self):
        # TODO: write this
        self.assert_("write this test" == None)
        v = Vector3d(0, 3, 0)
        self.assertEquals(math.degrees(v.phi), 90)
        v2 = Vector3d(v)
        v.rotateZ(math.radians(-90))
        self.assertEqual(math.degrees(v.angleTo(v2)), 90)
        self.assertAlmostEquals(v.angle(), 0)
        self.assertAlmostEquals(v.x, 3)
        self.assertAlmostEquals(v.y, 0)
        self.assertAlmostEquals((v.rotated(90) - v2).norm(), 0)
        self.assertEqual(v.norm(), v2.norm())
        v2.rotate(100)
        self.assertAlmostEquals(v.angleTo(v2), 170)
        v2.rotate(v2.angleTo(v))
        angle = v.angleTo(v2)
        self.assertAlmostEquals(v.angleTo(v2), 0)

    def testPickle(self):
        testvec = Vector3d(5, .3, -13)
        testvec_str = pickle.dumps(testvec)
        loaded_vec = pickle.loads(testvec_str)
        self.assertEquals(testvec, loaded_vec)
    # TODO: test error behaviour

def test():
    unittest.main()
    
if __name__ == "__main__":
    test()