var $builtinmodule = function(name)
{
    var mod = {};

    // todo; should probably put this in a math package
    mod.Mat44 = Sk.misceval.buildClass(mod, function($gbl, $loc)
            {
                $loc.__init__ = new Sk.builtin.func(function(self)
                    {
                        Sk.misceval.callsimArray($loc.loadIdentity, [self]);
                        self.stack = [];
                    });

                $loc.push = new Sk.builtin.func(function(self)
                    {
                        self.stack.push(self.elements.slice(0));
                    });

                $loc.pop = new Sk.builtin.func(function(self)
                    {
                        self.elements = self.stack.pop();
                    });

                $loc.loadIdentity = new Sk.builtin.func(function(self)
                    {
                        self.elements = [1.0, 0.0, 0.0, 0.0,
                                         0.0, 1.0, 0.0, 0.0,
                                         0.0, 0.0, 1.0, 0.0,
                                         0.0, 0.0, 0.0, 1.0];
                    });

                $loc.transform3 = new Sk.builtin.func(function(self, v)
                    {
                        var e = self.elements;
                        return Sk.misceval.callsimArray(mod.Vec3, [
                            e[0] * v.x + e[4] * v.y + e[8] * v.z,
                            e[1] * v.x + e[5] * v.y + e[9] * v.z,
                            e[2] * v.x + e[6] * v.y + e[10] * v.z]);
                    });

                $loc.scale = new Sk.builtin.func(function(self, sx, sy, sz)
                        {
                            self.elements[0*4+0] *= sx;
                            self.elements[0*4+1] *= sx;
                            self.elements[0*4+2] *= sx;
                            self.elements[0*4+3] *= sx;

                            self.elements[1*4+0] *= sy;
                            self.elements[1*4+1] *= sy;
                            self.elements[1*4+2] *= sy;
                            self.elements[1*4+3] *= sy;

                            self.elements[2*4+0] *= sz;
                            self.elements[2*4+1] *= sz;
                            self.elements[2*4+2] *= sz;
                            self.elements[2*4+3] *= sz;

                            return self;
                        });

                $loc.translate = new Sk.builtin.func(function(self, tx, ty, tz)
                        {
                            self.elements[3*4+0] += self.elements[0*4+0] * tx + self.elements[1*4+0] * ty + self.elements[2*4+0] * tz;
                            self.elements[3*4+1] += self.elements[0*4+1] * tx + self.elements[1*4+1] * ty + self.elements[2*4+1] * tz;
                            self.elements[3*4+2] += self.elements[0*4+2] * tx + self.elements[1*4+2] * ty + self.elements[2*4+2] * tz;
                            self.elements[3*4+3] += self.elements[0*4+3] * tx + self.elements[1*4+3] * ty + self.elements[2*4+3] * tz;
                            return self;
                        });

                $loc.rotate = new Sk.builtin.func(function(self, angle, x, y, z)
                        {
                            var mag = Math.sqrt(x*x + y*y + z*z);
                            var sinAngle = Math.sin(angle * Math.PI / 180.0);
                            var cosAngle = Math.cos(angle * Math.PI / 180.0);

                            if (mag > 0)
                            {
                                var xx, yy, zz, xy, yz, zx, xs, ys, zs;
                                var oneMinusCos;
                                var rotMat;

                                x /= mag;
                                y /= mag;
                                z /= mag;

                                xx = x * x;
                                yy = y * y;
                                zz = z * z;
                                xy = x * y;
                                yz = y * z;
                                zx = z * x;
                                xs = x * sinAngle;
                                ys = y * sinAngle;
                                zs = z * sinAngle;
                                oneMinusCos = 1.0 - cosAngle;

                                rotMat = Sk.misceval.callsimArray(mod.Mat44);

                                rotMat.elements[0*4+0] = (oneMinusCos * xx) + cosAngle;
                                rotMat.elements[0*4+1] = (oneMinusCos * xy) - zs;
                                rotMat.elements[0*4+2] = (oneMinusCos * zx) + ys;
                                rotMat.elements[0*4+3] = 0.0;

                                rotMat.elements[1*4+0] = (oneMinusCos * xy) + zs;
                                rotMat.elements[1*4+1] = (oneMinusCos * yy) + cosAngle;
                                rotMat.elements[1*4+2] = (oneMinusCos * yz) - xs;
                                rotMat.elements[1*4+3] = 0.0;

                                rotMat.elements[2*4+0] = (oneMinusCos * zx) - ys;
                                rotMat.elements[2*4+1] = (oneMinusCos * yz) + xs;
                                rotMat.elements[2*4+2] = (oneMinusCos * zz) + cosAngle;
                                rotMat.elements[2*4+3] = 0.0;

                                rotMat.elements[3*4+0] = 0.0;
                                rotMat.elements[3*4+1] = 0.0;
                                rotMat.elements[3*4+2] = 0.0;
                                rotMat.elements[3*4+3] = 1.0;

                                rotMat = rotMat.multiply(self);
                                self.elements = rotMat.elements;
                            }
                            return self;
                        });

                $loc.multiply = new Sk.builtin.func(function(self, right)
                        {
                            var tmp = Sk.misceval.callsimArray(mod.Mat44);

                            for (var i = 0; i < 4; i++)
                            {
                                tmp.elements[i*4+0] =
                                (self.elements[i*4+0] * right.elements[0*4+0]) +
                                (self.elements[i*4+1] * right.elements[1*4+0]) +
                                (self.elements[i*4+2] * right.elements[2*4+0]) +
                                (self.elements[i*4+3] * right.elements[3*4+0]) ;

                                tmp.elements[i*4+1] =
                                (self.elements[i*4+0] * right.elements[0*4+1]) +
                                (self.elements[i*4+1] * right.elements[1*4+1]) +
                                (self.elements[i*4+2] * right.elements[2*4+1]) +
                                (self.elements[i*4+3] * right.elements[3*4+1]) ;

                                tmp.elements[i*4+2] =
                                (self.elements[i*4+0] * right.elements[0*4+2]) +
                                (self.elements[i*4+1] * right.elements[1*4+2]) +
                                (self.elements[i*4+2] * right.elements[2*4+2]) +
                                (self.elements[i*4+3] * right.elements[3*4+2]) ;

                                tmp.elements[i*4+3] =
                                (self.elements[i*4+0] * right.elements[0*4+3]) +
                                (self.elements[i*4+1] * right.elements[1*4+3]) +
                                (self.elements[i*4+2] * right.elements[2*4+3]) +
                                (self.elements[i*4+3] * right.elements[3*4+3]) ;
                            }

                            self.elements = tmp.elements;
                            return self;
                        });

                /* Following gluLookAt implementation is adapted from
                 * the Mesa 3D Graphics library. http://www.mesa3d.org
                 */
                // todo; rewrite this with proper vec/mat ops
                $loc.lookAt = new Sk.builtin.func(function(self, eyeX, eyeY, eyeZ,
                                                                 centerX, centerY, centerZ,
                                                                 upX, upY, upZ)
                        {
                            /* Z vector */
                            var z = [
                                eyeX - centerX,
                                eyeY - centerY,
                                eyeZ - centerZ
                            ];
                            var mag = Math.sqrt(z[0] * z[0] + z[1] * z[1] + z[2] * z[2]);
                            if (mag)
                            {
                                z[0] /= mag;
                                z[1] /= mag;
                                z[2] /= mag;
                            }

                            /* Y vector */
                            var y = [ upX, upY, upZ ];

                            /* X vector = Y cross Z */
                            var x = [];
                            x[0] = y[1] * z[2] - y[2] * z[1];
                            x[1] = -y[0] * z[2] + y[2] * z[0];
                            x[2] = y[0] * z[1] - y[1] * z[0];

                            /* Recompute Y = Z cross X */
                            y[0] = z[1] * x[2] - z[2] * x[1];
                            y[1] = -z[0] * x[2] + z[2] * x[0];
                            y[2] = z[0] * x[1] - z[1] * x[0];

                            /* mpichler, 19950515 */
                            /* cross product gives area of parallelogram, which is < 1.0 for
                            * non-perpendicular unit-length vectors; so normalize x, y here
                            */

                            mag = Math.sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]);
                            if (mag) {
                                x[0] /= mag;
                                x[1] /= mag;
                                x[2] /= mag;
                            }

                            mag = Math.sqrt(y[0] * y[0] + y[1] * y[1] + y[2] * y[2]);
                            if (mag) {
                                y[0] /= mag;
                                y[1] /= mag;
                                y[2] /= mag;
                            }

                            var lookAt = Sk.misceval.callsimArray(mod.Mat44);
                            lookAt.elements[0 * 4 + 0] = x[0];
                            lookAt.elements[1 * 4 + 0] = x[1];
                            lookAt.elements[2 * 4 + 0] = x[2];
                            lookAt.elements[3 * 4 + 0] = 0.;
                            lookAt.elements[0 * 4 + 1] = y[0];
                            lookAt.elements[1 * 4 + 1] = y[1];
                            lookAt.elements[2 * 4 + 1] = y[2];
                            lookAt.elements[3 * 4 + 1] = 0.;
                            lookAt.elements[0 * 4 + 2] = z[0];
                            lookAt.elements[1 * 4 + 2] = z[1];
                            lookAt.elements[2 * 4 + 2] = z[2];
                            lookAt.elements[3 * 4 + 2] = 0.;
                            lookAt.elements[0 * 4 + 3] = 0.;
                            lookAt.elements[1 * 4 + 3] = 0.;
                            lookAt.elements[2 * 4 + 3] = 0.;
                            lookAt.elements[3 * 4 + 3] = 1.;

                            // log(lookAt.elements);

                            lookAt = lookAt.multiply(self);
                            self.elements = lookAt.elements;
                            self.translate(-eyeX, -eyeY, -eyeZ);

                            // log(this.elements);

                            return self;
                        });
            },
            'Mat44', []);

    // todo; should probably put this in a math package
    mod.Mat33 = Sk.misceval.buildClass(mod, function($gbl, $loc)
            {
                $loc.__init__ = new Sk.builtin.func(function(self)
                    {
                        Sk.misceval.callsimArray($loc.loadIdentity, [self]);
                    });

                $loc.loadIdentity = new Sk.builtin.func(function(self)
                    {
                        self.elements = [1.0, 0.0, 0.0,
                                         0.0, 1.0, 0.0,
                                         0.0, 0.0, 1.0];
                    });
            },
            'Mat33', []);

    mod.Vec3 = Sk.misceval.buildClass(mod, function($gbl, $loc)
            {
                $loc.__init__ = new Sk.builtin.func(function(self, x, y, z)
                    {
                        self.x = x;
                        self.y = y;
                        self.z = z;
                    });
                $loc.__sub__ = new Sk.builtin.func(function(self, other)
                    {
                        return Sk.misceval.callsimArray(mod.Vec3, [self.x - other.x, self.y - other.y, self.z - other.z]);
                    });
            },
            'Vec3', []);
    
    mod.cross = new Sk.builtin.func(function(v1, v2)
            {
                Sk.asserts.assert(v1 instanceof mod.Vec3 && v2 instanceof mod.Vec3);
                return Sk.misceval.callsimArray(mod.Vec3, [
                    v1.y * v2.z - v1.z * v2.y,
                    v1.z * v2.x - v1.x * v2.z,
                    v1.x * v2.y - v1.y * v2.x]);
            });

    return mod;
};
