// Copyright 2020 The Defold Foundation
// Licensed under the Defold License version 1.0 (the "License"); you may not use
// this file except in compliance with the License.
//
// You may obtain a copy of the License, together with FAQs at
// https://www.defold.com/license
//
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.

#include "script.h"

#include <dmsdk/vectormath/cpp/vectormath_aos.h>
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <dlib/vmath.h>

#include <dlib/dstrings.h>
#include <dlib/log.h>

extern "C"
{
#include <lua/lauxlib.h>
#include <lua/lualib.h>
}

namespace dmScript
{
    /*# Vector math API documentation
     *
     * Functions for mathematical operations on vectors, matrices and quaternions.
     *
     * - The vector types (`vmath.vector3` and `vmath.vector4`) supports addition and subtraction
     *   with vectors of the same type. Vectors can be negated and multiplied (scaled) or divided by numbers.
     * - The quaternion type (`vmath.quat`) supports multiplication with other quaternions.
     * - The matrix type (`vmath.matrix4`) can be multiplied with numbers, other matrices
     *   and `vmath.vector4` values.
     * - All types performs equality comparison by each component value.
     *
     * The following components are available for the various types:
     *
     * vector3
     * : `x`, `y` and `z`. Example: `v.y`
     *
     * vector4
     * : `x`, `y`, `z`, and `w`. Example: `v.w`
     *
     * quaternion
     * : `x`, `y`, `z`, and `w`. Example: `q.w`
     *
     * matrix4
     * : `m00` to `m33` where the first number is the row (starting from 0) and the second
     * number is the column. Columns can be accessed with `c0` to `c3`, returning a `vector4`.
     * Example: `m.m21` which is equal to `m.c1.z`
     *
     * vector
     * : indexed by number 1 to the vector length. Example: `v[3]`
     *
     * @document
     * @name Vector math
     * @namespace vmath
     */

#define SCRIPT_LIB_NAME "vmath"
#define SCRIPT_TYPE_NAME_VECTOR "vector" // Vector of unspecific length
#define SCRIPT_TYPE_NAME_VECTOR3 "vector3"
#define SCRIPT_TYPE_NAME_VECTOR4 "vector4"
#define SCRIPT_TYPE_NAME_QUAT "quat"
#define SCRIPT_TYPE_NAME_MATRIX4 "matrix4"

    enum ScriptUserType
    {
        SCRIPT_TYPE_VECTOR3,
        SCRIPT_TYPE_VECTOR4,
        SCRIPT_TYPE_QUAT,
        SCRIPT_TYPE_MATRIX4,
        SCRIPT_TYPE_VECTOR,
        SCRIPT_TYPE_UNKNOWN,
    };

    uint32_t TYPE_HASHES[SCRIPT_TYPE_UNKNOWN];

    static ScriptUserType GetType(lua_State* L, int index)
    {
        uint32_t type_hash = dmScript::GetUserType(L, index);
        for (uint32_t i = 0; i < SCRIPT_TYPE_UNKNOWN; ++i)
        {
            if (TYPE_HASHES[i] == type_hash)
            {
                return (ScriptUserType)i;
            }
        }
        return SCRIPT_TYPE_UNKNOWN;
    }

    bool IsVector(lua_State *L, int index)
    {
        return dmScript::GetUserType(L, index) == TYPE_HASHES[SCRIPT_TYPE_VECTOR];
    }

    static const luaL_reg Vector_methods[] =
    {
        {0,0}
    };

    static int Vector_len(lua_State *L)
    {
        dmVMath::FloatVector* v = *(dmVMath::FloatVector**)lua_touserdata(L, 1);

        lua_pushnumber(L, v->size);
        return 1;
    }

    static int Vector_index(lua_State *L)
    {
        dmVMath::FloatVector* v = *(dmVMath::FloatVector**)lua_touserdata(L, 1);

        int key = luaL_checkinteger(L, 2);
        if (key > 0 && key <= v->size)
        {
            lua_pushnumber(L, v->values[key-1]);
            return 1;
        }
        else
        {
            if (v->size > 0)
            {
                return luaL_error(L, "%s.%s only has valid indices between 1 and %d.", SCRIPT_LIB_NAME, SCRIPT_TYPE_NAME_VECTOR, v->size);
            }

            return luaL_error(L, "%s.%s has no addressable indices, size is 0.", SCRIPT_LIB_NAME, SCRIPT_TYPE_NAME_VECTOR);
        }
    }

    static int Vector_newindex(lua_State *L)
    {
        dmVMath::FloatVector* v = *(dmVMath::FloatVector**)lua_touserdata(L, 1);

        int key = luaL_checkinteger(L, 2);
        if (key > 0 && key <= v->size)
        {
            v->values[key-1] = (float)luaL_checknumber(L, 3);
        }
        else
        {
            if (v->size > 0)
            {
                return luaL_error(L, "%s.%s only has valid indices between 1 and %d.", SCRIPT_LIB_NAME, SCRIPT_TYPE_NAME_VECTOR, v->size);
            }

            return luaL_error(L, "%s.%s has no addressable indices, size is 0.", SCRIPT_LIB_NAME, SCRIPT_TYPE_NAME_VECTOR);
        }
        return 0;
    }

    static int Vector_tostring(lua_State *L)
    {
        dmVMath::FloatVector* v = *(dmVMath::FloatVector**)lua_touserdata(L, 1);
        lua_pushfstring(L, "%s.%s (size: %d)", SCRIPT_LIB_NAME, SCRIPT_TYPE_NAME_VECTOR, v->size);
        return 1;
    }

    static int Vector_gc(lua_State *L)
    {
        dmVMath::FloatVector* v = *(dmVMath::FloatVector**)lua_touserdata(L, 1);
        delete v;
        return 0;
    }

    static const luaL_reg Vector_meta[] =
    {
        {"__gc",        Vector_gc},
        {"__tostring",  Vector_tostring},
        {"__len",       Vector_len},
        {"__index",     Vector_index},
        {"__newindex",  Vector_newindex},
        {0,0}
    };

    bool IsVector3(lua_State *L, int index)
    {
        return dmScript::GetUserType(L, index) == TYPE_HASHES[SCRIPT_TYPE_VECTOR3];
    }

    Vectormath::Aos::Vector3* ToVector3(lua_State* L, int index)
    {
        return (Vectormath::Aos::Vector3*)ToUserType(L, index, TYPE_HASHES[SCRIPT_TYPE_VECTOR3]);
    }

    static int Vector3_tostring(lua_State *L)
    {
        Vectormath::Aos::Vector3* v = (Vectormath::Aos::Vector3*)lua_touserdata(L, 1);
        lua_pushfstring(L, "vmath.%s(%f, %f, %f)", SCRIPT_TYPE_NAME_VECTOR3, v->getX(), v->getY(), v->getZ());
        return 1;
    }

    static int Vector3_index(lua_State *L)
    {
        Vectormath::Aos::Vector3* v = (Vectormath::Aos::Vector3*)lua_touserdata(L, 1);

        const char* key = luaL_checkstring(L, 2);
        if (key[0] == 'x')
        {
            lua_pushnumber(L, v->getX());
            return 1;
        }
        else if (key[0] == 'y')
        {
            lua_pushnumber(L, v->getY());
            return 1;
        }
        else if (key[0] == 'z')
        {
            lua_pushnumber(L, v->getZ());
            return 1;
        }
        else
        {
            return luaL_error(L, "%s.%s only has fields x, y, z.", SCRIPT_LIB_NAME, SCRIPT_TYPE_NAME_VECTOR3);
        }
    }

    static int Vector3_newindex(lua_State *L)
    {
        Vectormath::Aos::Vector3* v = (Vectormath::Aos::Vector3*)lua_touserdata(L, 1);

        const char* key = luaL_checkstring(L, 2);
        if (key[0] == 'x')
        {
            v->setX((float) luaL_checknumber(L, 3));
        }
        else if (key[0] == 'y')
        {
            v->setY((float) luaL_checknumber(L, 3));
        }
        else if (key[0] == 'z')
        {
            v->setZ((float) luaL_checknumber(L, 3));
        }
        else
        {
            return luaL_error(L, "%s.%s only has fields x, y, z.", SCRIPT_LIB_NAME, SCRIPT_TYPE_NAME_VECTOR3);
        }
        return 0;
    }

    static int Vector3_add(lua_State *L)
    {
        Vectormath::Aos::Vector3* v1 = CheckVector3(L, 1);
        Vectormath::Aos::Vector3* v2 = CheckVector3(L, 2);
        PushVector3(L, *v1 + *v2);
        return 1;
    }

    static int Vector3_sub(lua_State *L)
    {
        Vectormath::Aos::Vector3* v1 = CheckVector3(L, 1);
        Vectormath::Aos::Vector3* v2 = CheckVector3(L, 2);
        PushVector3(L, *v1 - *v2);
        return 1;
    }

    static int Vector3_mul(lua_State *L)
    {
        Vectormath::Aos::Vector3* v = ToVector3(L, 1);
        float s;
        if (v != 0)
        {
            s = (float) luaL_checknumber(L, 2);
        }
        else
        {
            s = (float) luaL_checknumber(L, 1);
            v = CheckVector3(L, 2);
        }
        PushVector3(L, *v * s);
        return 1;
    }

    static int Vector3_div(lua_State *L)
    {
        Vectormath::Aos::Vector3* v = CheckVector3(L, 1);
        float s = (float) luaL_checknumber(L, 2);
        PushVector3(L, *v / s);
        return 1;
    }

    static int Vector3_unm(lua_State *L)
    {
        Vectormath::Aos::Vector3* v = (Vectormath::Aos::Vector3*)lua_touserdata(L, 1);
        PushVector3(L, - *v);
        return 1;
    }

    static int Vector3_concat(lua_State *L)
    {
        size_t size = 0;
        const char* s = luaL_checklstring(L, 1, &size);
        Vectormath::Aos::Vector3* v = CheckVector3(L, 2);
        char* buffer = new char[size + 61];
        dmSnPrintf(buffer, size, "%svmath.%s(%f, %f, %f)", s, SCRIPT_TYPE_NAME_VECTOR3, v->getX(), v->getY(), v->getZ());
        lua_pushstring(L, buffer);
        delete [] buffer;
        return 1;
    }

    static int Vector3_eq(lua_State *L)
    {
        Vectormath::Aos::Vector3* v1 = ToVector3(L, 1);
        Vectormath::Aos::Vector3* v2 = ToVector3(L, 2);
        lua_pushboolean(L, v1 && v2 && v1->getX() == v2->getX() && v1->getY() == v2->getY() && v1->getZ() == v2->getZ());
        return 1;
    }

    static const luaL_reg Vector3_methods[] =
    {
        {0,0}
    };
    static const luaL_reg Vector3_meta[] =
    {
        {"__tostring",  Vector3_tostring},
        {"__index",     Vector3_index},
        {"__newindex",  Vector3_newindex},
        {"__add", Vector3_add},
        {"__sub", Vector3_sub},
        {"__mul", Vector3_mul},
        {"__div", Vector3_div},
        {"__unm", Vector3_unm},
        {"__concat", Vector3_concat},
        {"__eq", Vector3_eq},
        {0,0}
    };

    bool IsVector4(lua_State *L, int index)
    {
        return dmScript::GetUserType(L, index) == TYPE_HASHES[SCRIPT_TYPE_VECTOR4];
    }

    Vectormath::Aos::Vector4* ToVector4(lua_State *L, int index)
    {
        return (Vectormath::Aos::Vector4*)ToUserType(L, index, TYPE_HASHES[SCRIPT_TYPE_VECTOR4]);
    }

    static int Vector4_tostring(lua_State *L)
    {
        Vectormath::Aos::Vector4* v = (Vectormath::Aos::Vector4*)lua_touserdata(L, 1);
        lua_pushfstring(L, "vmath.%s(%f, %f, %f, %f)", SCRIPT_TYPE_NAME_VECTOR4, v->getX(), v->getY(), v->getZ(), v->getW());
        return 1;
    }

    static int Vector4_index(lua_State *L)
    {
        Vectormath::Aos::Vector4* v = (Vectormath::Aos::Vector4*)lua_touserdata(L, 1);

        const char* key = luaL_checkstring(L, 2);
        if (key[0] == 'x')
        {
            lua_pushnumber(L, v->getX());
            return 1;
        }
        else if (key[0] == 'y')
        {
            lua_pushnumber(L, v->getY());
            return 1;
        }
        else if (key[0] == 'z')
        {
            lua_pushnumber(L, v->getZ());
            return 1;
        }
        else if (key[0] == 'w')
        {
            lua_pushnumber(L, v->getW());
            return 1;
        }
        else
        {
            return luaL_error(L, "%s.%s only has fields x, y, z, w.", SCRIPT_LIB_NAME, SCRIPT_TYPE_NAME_VECTOR4);
        }
    }

    static int Vector4_newindex(lua_State *L)
    {
        Vectormath::Aos::Vector4* v = (Vectormath::Aos::Vector4*)lua_touserdata(L, 1);

        const char* key = luaL_checkstring(L, 2);
        if (key[0] == 'x')
        {
            v->setX((float) luaL_checknumber(L, 3));
        }
        else if (key[0] == 'y')
        {
            v->setY((float) luaL_checknumber(L, 3));
        }
        else if (key[0] == 'z')
        {
            v->setZ((float) luaL_checknumber(L, 3));
        }
        else if (key[0] == 'w')
        {
            v->setW((float) luaL_checknumber(L, 3));
        }
        else
        {
            return luaL_error(L, "%s.%s only has fields x, y, z, w.", SCRIPT_LIB_NAME, SCRIPT_TYPE_NAME_VECTOR4);
        }
        return 0;
    }

    static int Vector4_add(lua_State *L)
    {
        Vectormath::Aos::Vector4* v1 = CheckVector4(L, 1);
        Vectormath::Aos::Vector4* v2 = CheckVector4(L, 2);
        PushVector4(L, *v1 + *v2);
        return 1;
    }

    static int Vector4_sub(lua_State *L)
    {
        Vectormath::Aos::Vector4* v1 = CheckVector4(L, 1);
        Vectormath::Aos::Vector4* v2 = CheckVector4(L, 2);
        PushVector4(L, *v1 - *v2);
        return 1;
    }

    static int Vector4_mul(lua_State *L)
    {
        Vectormath::Aos::Vector4* v = ToVector4(L, 1);
        float s;
        if (v != 0)
        {
            s = (float) luaL_checknumber(L, 2);
        }
        else
        {
            s = (float) luaL_checknumber(L, 1);
            v = CheckVector4(L, 2);
        }
        PushVector4(L, *v * s);
        return 1;
    }

    static int Vector4_div(lua_State *L)
    {
        Vectormath::Aos::Vector4* v = CheckVector4(L, 1);
        float s = (float) luaL_checknumber(L, 2);
        PushVector4(L, *v / s);
        return 1;
    }

    static int Vector4_unm(lua_State *L)
    {
        Vectormath::Aos::Vector4* v = (Vectormath::Aos::Vector4*)lua_touserdata(L, 1);
        PushVector4(L, - *v);
        return 1;
    }

    static int Vector4_concat(lua_State *L)
    {
        size_t size = 0;
        const char* s = luaL_checklstring(L, 1, &size);
        Vectormath::Aos::Vector4* v = CheckVector4(L, 2);
        char* buffer = new char[size + 77];
        dmSnPrintf(buffer, size, "%svmath.%s(%f, %f, %f, %f)", s, SCRIPT_TYPE_NAME_VECTOR4, v->getX(), v->getY(), v->getZ(), v->getW());
        lua_pushstring(L, buffer);
        delete [] buffer;
        return 1;
    }

    static int Vector4_eq(lua_State *L)
    {
        Vectormath::Aos::Vector4* v1 = ToVector4(L, 1);
        Vectormath::Aos::Vector4* v2 = ToVector4(L, 2);
        lua_pushboolean(L, v1 && v2 && v1->getX() == v2->getX() && v1->getY() == v2->getY() && v1->getZ() == v2->getZ() && v1->getW() == v2->getW());
        return 1;
    }

    static const luaL_reg Vector4_methods[] =
    {
        {0,0}
    };
    static const luaL_reg Vector4_meta[] =
    {
        {"__tostring",  Vector4_tostring},
        {"__index",     Vector4_index},
        {"__newindex",  Vector4_newindex},
        {"__add",       Vector4_add},
        {"__sub",       Vector4_sub},
        {"__mul",       Vector4_mul},
        {"__div",       Vector4_div},
        {"__unm",       Vector4_unm},
        {"__concat",    Vector4_concat},
        {"__eq",        Vector4_eq},
        {0,0}
    };

    Vectormath::Aos::Quat* ToQuat(lua_State *L, int index)
    {
        return (Vectormath::Aos::Quat*)ToUserType(L, index, TYPE_HASHES[SCRIPT_TYPE_QUAT]);
    }

    static int Quat_tostring(lua_State *L)
    {
        Vectormath::Aos::Quat* q = (Vectormath::Aos::Quat*)lua_touserdata(L, 1);
        lua_pushfstring(L, "vmath.%s(%f, %f, %f, %f)", SCRIPT_TYPE_NAME_QUAT, q->getX(), q->getY(), q->getZ(), q->getW());
        return 1;
    }

    static int Quat_index(lua_State *L)
    {
        Vectormath::Aos::Quat* q = (Vectormath::Aos::Quat*)lua_touserdata(L, 1);

        const char* key = luaL_checkstring(L, 2);
        if (key[0] == 'x')
        {
            lua_pushnumber(L, q->getX());
            return 1;
        }
        else if (key[0] == 'y')
        {
            lua_pushnumber(L, q->getY());
            return 1;
        }
        else if (key[0] == 'z')
        {
            lua_pushnumber(L, q->getZ());
            return 1;
        }
        else if (key[0] == 'w')
        {
            lua_pushnumber(L, q->getW());
            return 1;
        }
        else
        {
            return luaL_error(L, "%s.%s only has fields x, y, z, w.", SCRIPT_LIB_NAME, SCRIPT_TYPE_NAME_QUAT);
        }
        return 0;
    }

    static int Quat_newindex(lua_State *L)
    {
        Vectormath::Aos::Quat* q = (Vectormath::Aos::Quat*)lua_touserdata(L, 1);

        const char* key = luaL_checkstring(L, 2);
        if (key[0] == 'x')
        {
            q->setX((float) luaL_checknumber(L, -1));
        }
        else if (key[0] == 'y')
        {
            q->setY((float) luaL_checknumber(L, -1));
        }
        else if (key[0] == 'z')
        {
            q->setZ((float) luaL_checknumber(L, -1));
        }
        else if (key[0] == 'w')
        {
            q->setW((float) luaL_checknumber(L, -1));
        }
        else
        {
            return luaL_error(L, "%s.%s only has fields x, y, z, w.", SCRIPT_LIB_NAME, SCRIPT_TYPE_NAME_QUAT);
        }
        return 0;
    }

    static int Quat_mul(lua_State *L)
    {
        Vectormath::Aos::Quat* q1 = CheckQuat(L, 1);
        Vectormath::Aos::Quat* q2 = CheckQuat(L, 2);
        PushQuat(L, *q1 * *q2);
        return 1;
    }

    static int Quat_concat(lua_State *L)
    {
        size_t size = 0;
        const char* s = luaL_checklstring(L, 1, &size);
        Vectormath::Aos::Quat* q = CheckQuat(L, 2);
        char* buffer = new char[size + 74];
        dmSnPrintf(buffer, size, "%svmath.%s(%f, %f, %f, %f)", s, SCRIPT_TYPE_NAME_QUAT, q->getX(), q->getY(), q->getZ(), q->getW());
        lua_pushstring(L, buffer);
        delete [] buffer;
        return 1;
    }

    static int Quat_eq(lua_State *L)
    {
        Vectormath::Aos::Quat* q1 = ToQuat(L, 1);
        Vectormath::Aos::Quat* q2 = ToQuat(L, 2);
        lua_pushboolean(L, q1 && q2 && q1->getX() == q2->getX() && q1->getY() == q2->getY() && q1->getZ() == q2->getZ() && q1->getW() == q2->getW());
        return 1;
    }

    static const luaL_reg Quat_methods[] =
    {
        {0,0}
    };
    static const luaL_reg Quat_meta[] =
    {
        {"__tostring",  Quat_tostring},
        {"__index",     Quat_index},
        {"__newindex",  Quat_newindex},
        {"__mul",       Quat_mul},
        {"__concat",    Quat_concat},
        {"__eq",        Quat_eq},
        {0,0}
    };

    Vectormath::Aos::Matrix4* ToMatrix4(lua_State *L, int index)
    {
        return (Vectormath::Aos::Matrix4*)ToUserType(L, index, TYPE_HASHES[SCRIPT_TYPE_MATRIX4]);
    }

    static int Matrix4_tostring(lua_State *L)
    {
        Vectormath::Aos::Matrix4* m = (Vectormath::Aos::Matrix4*)lua_touserdata(L, 1);
        lua_pushfstring(L, "vmath.%s(%f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f)", SCRIPT_TYPE_NAME_MATRIX4,
            m->getElem(0, 0), m->getElem(1, 0), m->getElem(2, 0), m->getElem(3, 0),
            m->getElem(0, 1), m->getElem(1, 1), m->getElem(2, 1), m->getElem(3, 1),
            m->getElem(0, 2), m->getElem(1, 2), m->getElem(2, 2), m->getElem(3, 2),
            m->getElem(0, 3), m->getElem(1, 3), m->getElem(2, 3), m->getElem(3, 3));
        return 1;
    }

    static int Matrix4_index(lua_State *L)
    {
        Vectormath::Aos::Matrix4* m = (Vectormath::Aos::Matrix4*)lua_touserdata(L, 1);

        size_t key_len = 0;
        const char* key = luaL_checklstring(L, 2, &key_len);
        if (key_len == 3)
        {
            int row = key[1] - (char)'0';
            int col = key[2] - (char)'0';
            if (0 <= row && row < 4 && 0 <= col && col < 4)
            {
                lua_pushnumber(L, m->getElem(col, row));
                return 1;
            }
        }
        else if (key_len == 2)
        {
            int col = key[1] - (char)'0';
            if (0 <= col && col < 4)
            {
                PushVector4(L, m->getCol(col));
                return 1;
            }
        }
        return luaL_error(L, "%s.%s only has fields c0, ..., c3 and m00, m01, ..., m10, ..., m33.", SCRIPT_LIB_NAME, SCRIPT_TYPE_NAME_MATRIX4);
    }

    static int Matrix4_newindex(lua_State *L)
    {
        Vectormath::Aos::Matrix4* m = (Vectormath::Aos::Matrix4*)lua_touserdata(L, 1);

        size_t key_len = 0;
        const char* key = luaL_checklstring(L, 2, &key_len);
        if (key_len == 3)
        {
            int row = key[1] - (char)'0';
            int col = key[2] - (char)'0';
            if (0 <= row && row < 4 && 0 <= col && col < 4)
            {
                m->setElem(col, row, (float) luaL_checknumber(L, -1));
                return 0;
            }
        }
        else if (key_len == 2)
        {
            int col = key[1] - (char)'0';
            if (0 <= col && col < 4)
            {
                Vectormath::Aos::Vector4* vec = CheckVector4(L, -1);
                m->setCol(col, *vec);
                return 0;
            }
        }
        return luaL_error(L, "%s.%s only has fields c0, ..., c3 and m00, m01, ..., m10, ..., m33.", SCRIPT_LIB_NAME, SCRIPT_TYPE_NAME_MATRIX4);
    }

    static int Matrix4_mul(lua_State *L)
    {
        Vectormath::Aos::Matrix4 m1;
        if (lua_isnumber(L, 1))
        {
            float f = (float) lua_tonumber(L, 1);
            m1 = *CheckMatrix4(L, 2);
            PushMatrix4(L, m1 * f);
        }
        else
        {
            m1 = *CheckMatrix4(L, 1);
            Vectormath::Aos::Matrix4* m2 = ToMatrix4(L, 2);
            Vectormath::Aos::Vector4* v = m2 != 0 ? 0 : ToVector4(L, 2);
            if (m2 != 0)
            {
                PushMatrix4(L, m1 * *m2);
            }
            else if (v != 0)
            {
                PushVector4(L, m1 * *v);
            }
            else if (lua_isnumber(L, 2))
            {
                float f = (float) luaL_checknumber(L, 2);
                PushMatrix4(L, m1 * f);
            }
            else
            {
                return luaL_error(L, "%s.%s can only be multiplied with a number, another %s or a %s.", SCRIPT_LIB_NAME, SCRIPT_TYPE_NAME_MATRIX4, SCRIPT_TYPE_NAME_MATRIX4, SCRIPT_TYPE_NAME_VECTOR4);
            }
        }
        return 1;
    }

    static int Matrix4_concat(lua_State *L)
    {
        size_t size = 0;
        const char* s = luaL_checklstring(L, 1, &size);
        Vectormath::Aos::Matrix4* m = CheckMatrix4(L, 2);
        char* buffer = new char[size + 268];
        dmSnPrintf(buffer, size, "%svmath.%s(%f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f)", s, SCRIPT_TYPE_NAME_MATRIX4,
            m->getElem(0, 0), m->getElem(1, 0), m->getElem(2, 0), m->getElem(3, 0),
            m->getElem(0, 1), m->getElem(1, 1), m->getElem(2, 1), m->getElem(3, 1),
            m->getElem(0, 2), m->getElem(1, 2), m->getElem(2, 2), m->getElem(3, 2),
            m->getElem(0, 3), m->getElem(1, 3), m->getElem(2, 3), m->getElem(3, 3));
        lua_pushstring(L, buffer);
        delete [] buffer;
        return 1;
    }

    static int Matrix4_eq(lua_State *L)
    {
        Vectormath::Aos::Matrix4* m1 = ToMatrix4(L, 1);
        Vectormath::Aos::Matrix4* m2 = ToMatrix4(L, 2);
        lua_pushboolean(L,
            m1 && m2 &&
            m1->getElem(0, 0) == m2->getElem(0, 0) && m1->getElem(1, 0) == m2->getElem(1, 0) && m1->getElem(2, 0) == m2->getElem(2, 0) && m1->getElem(3, 0) == m2->getElem(3, 0) &&
            m1->getElem(0, 1) == m2->getElem(0, 1) && m1->getElem(1, 1) == m2->getElem(1, 1) && m1->getElem(2, 1) == m2->getElem(2, 1) && m1->getElem(3, 1) == m2->getElem(3, 1) &&
            m1->getElem(0, 2) == m2->getElem(0, 2) && m1->getElem(1, 2) == m2->getElem(1, 2) && m1->getElem(2, 2) == m2->getElem(2, 2) && m1->getElem(3, 2) == m2->getElem(3, 2) &&
            m1->getElem(0, 3) == m2->getElem(0, 3) && m1->getElem(1, 3) == m2->getElem(1, 3) && m1->getElem(2, 3) == m2->getElem(2, 3) && m1->getElem(3, 3) == m2->getElem(3, 3));
        return 1;
    }

    static const luaL_reg Matrix4_methods[] =
    {
        {0,0}
    };
    static const luaL_reg Matrix4_meta[] =
    {
        {"__tostring",  Matrix4_tostring},
        {"__index",     Matrix4_index},
        {"__newindex",  Matrix4_newindex},
        {"__mul",       Matrix4_mul},
        {"__concat",    Matrix4_concat},
        {"__eq",        Matrix4_eq},
        {0,0}
    };

    /*# create a new vector from a table of values
     * Creates a vector of arbitrary size. The vector is initialized
     * with numeric values from a table.
     *
     * [icon:attention] The table values are converted to floating point
     * values. If a value cannot be converted, a 0 is stored in that
     * value position in the vector.
     *
     * @name vmath.vector
     * @param t [type:table] table of numbers
     * @return v [type:vector] new vector
     * @examples
     *
     * How to create a vector with custom data to be used for animation easing:
     *
     * ```lua
     * local values = { 0, 0.5, 0 }
     * local vec = vmath.vector(values)
     * print(vec) --> vmath.vector (size: 3)
     * print(vec[2]) --> 0.5
     * ```
     */
    static int Vector_new(lua_State* L)
    {
        dmVMath::FloatVector *v;
        if (lua_gettop(L) == 0)
        {
            v = new dmVMath::FloatVector(0);
        }
        else
        {
            luaL_checktype(L, 1, LUA_TTABLE);
            int array_size = lua_objlen(L, 1);
            v = new dmVMath::FloatVector(array_size);

            for (int i = 0; i < array_size; i++)
            {
                lua_pushnumber(L, i+1);
                lua_gettable(L, 1);
                v->values[i] = (float) lua_tonumber(L, -1);
                lua_pop(L, 1);
            }
        }

        PushVector(L, v);
        return 1;
    }

    /*# creates a new zero vector
     *
     * Creates a new zero vector with all components set to 0.
     *
     * @name vmath.vector3
     * @return v [type:vector3] new zero vector
     * @examples
     *
     * ```lua
     * local vec = vmath.vector3()
     * pprint(vec) --> vmath.vector3(0, 0, 0)
     * print(vec.x) --> 0
     * ```
     */

    /*# creates a new vector from scalar value
     *
     * Creates a new vector with all components set to the
     * supplied scalar value.
     *
     * @name vmath.vector3
     * @param n [type:number] scalar value to splat
     * @return v [type:vector3] new vector
     * @examples
     *
     * ```lua
     * local vec = vmath.vector3(1.0)
     * print(vec) --> vmath.vector3(1, 1, 1)
     * print(vec.x) --> 1
     * ```
     */

    /*# creates a new vector from another existing vector
     *
     * Creates a new vector with all components set to the
     * corresponding values from the supplied vector. I.e.
     * This function creates a copy of the given vector.
     *
     * @name vmath.vector3
     * @param v1 [type:vector3] existing vector
     * @return v [type:vector3] new vector
     * @examples
     *
     * ```lua
     * local vec1 = vmath.vector3(1.0)
     * local vec2 = vmath.vector3(vec1)
     * if vec1 == vec2 then
     *     -- yes, they are equal
     *     print(vec2) --> vmath.vector3(1, 1, 1)
     * end
     * ```
     */

    /*# creates a new vector from its coordinates
     *
     * Creates a new vector with the components set to the
     * supplied values.
     *
     * @name vmath.vector3
     * @param x [type:number] x coordinate
     * @param y [type:number] y coordinate
     * @param z [type:number] z coordinate
     * @return v [type:vector3] new vector
     * @examples
     *
     * ```lua
     * local vec = vmath.vector3(1.0, 2.0, 3.0)
     * print(vec) --> vmath.vector3(1, 2, 3)
     * print(-vec) --> vmath.vector3(-1, -2, -3)
     * print(vec * 2) --> vmath.vector3(2, 4, 6)
     * print(vec + vmath.vector3(2.0)) --> vmath.vector3(3, 4, 5)
     * print(vec - vmath.vector3(2.0)) --> vmath.vector3(-1, 0, 1)
     * ```
     */
    static int Vector3_new(lua_State* L)
    {
        Vectormath::Aos::Vector3 v;
        if (lua_gettop(L) == 0)
        {
            v = Vectormath::Aos::Vector3(0.0f, 0.0f, 0.0f);
        }
        else if (lua_gettop(L) == 1)
        {
            int type = lua_type(L, -1);
            if (type == LUA_TNUMBER)
            {
                float x = (float) lua_tonumber(L, -1);
                v = Vectormath::Aos::Vector3(x, x, x);
            }
            else
            {
                // If not number assume vector3
                v = *CheckVector3(L, -1);
            }
        }
        else
        {
            v.setX((float) luaL_checknumber(L, 1));
            v.setY((float) luaL_checknumber(L, 2));
            v.setZ((float) luaL_checknumber(L, 3));
        }
        PushVector3(L, v);
        return 1;
    }

    /*# creates a new zero vector
     *
     * Creates a new zero vector with all components set to 0.
     *
     * @name vmath.vector4
     * @return v [type:vector4] new zero vector
     * @examples
     *
     * ```lua
     * local vec = vmath.vector4()
     * print(vec) --> vmath.vector4(0, 0, 0, 0)
     * print(vec.w) --> 0
     * ```
     */

    /*# creates a new vector from scalar value
     *
     * Creates a new vector with all components set to the
     * supplied scalar value.
     *
     * @name vmath.vector4
     * @param n [type:number] scalar value to splat
     * @return v [type:vector4] new vector
     * @examples
     *
     * ```lua
     * local vec = vmath.vector4(1.0)
     * print(vec) --> vmath.vector4(1, 1, 1, 1)
     * print(vec.w) --> 1
     * ```
     */

    /*# creates a new vector from another existing vector
     *
     * Creates a new vector with all components set to the
     * corresponding values from the supplied vector. I.e.
     * This function creates a copy of the given vector.
     *
     * @name vmath.vector4
     * @param v1 [type:vector4] existing vector
     * @return v [type:vector4] new vector
     * @examples
     *
     * ```lua
     * local vect1 = vmath.vector4(1.0)
     * local vect2 = vmath.vector4(vec1)
     * if vec1 == vec2 then
     *     -- yes, they are equal
     *     print(vec2) --> vmath.vector4(1, 1, 1, 1)
     * end
     * ```
     */

    /*# creates a new vector from its coordinates
     *
     * Creates a new vector with the components set to the
     * supplied values.
     *
     * @name vmath.vector4
     * @param x [type:number] x coordinate
     * @param y [type:number] y coordinate
     * @param z [type:number] z coordinate
     * @param w [type:number] w coordinate
     * @return v [type:vector4] new vector
     * @examples
     *
     * ```lua
     * local vec = vmath.vector4(1.0, 2.0, 3.0, 4.0)
     * print(vec) --> vmath.vector4(1, 2, 3, 4)
     * print(-vec) --> vmath.vector4(-1, -2, -3, -4)
     * print(vec * 2) --> vmath.vector4(2, 4, 6, 8)
     * print(vec + vmath.vector4(2.0)) --> vmath.vector4(3, 4, 5, 6)
     * print(vec - vmath.vector4(2.0)) --> vmath.vector4(-1, 0, 1, 2)
     * ```
     */
    static int Vector4_new(lua_State* L)
    {
        Vectormath::Aos::Vector4 v;
        // NOTE: The following comment is obsolete
        // "No empty constructor, since the value of w matters"
        // Don't understand why. Every component matters :-)
        // Empty constructor added.
        if (lua_gettop(L) == 0)
        {
            v = Vectormath::Aos::Vector4(0.0f, 0.0f, 0.0f, 0.0f);
        }
        else if (lua_gettop(L) == 1)
        {
            int type = lua_type(L, -1);
            if (type == LUA_TNUMBER)
            {
                float x = (float) lua_tonumber(L, -1);
                v = Vectormath::Aos::Vector4(x, x, x, x);
            }
            else
            {
                // If not number assume vector4
                v = *CheckVector4(L, -1);
            }
        }
        else
        {
            v.setX((float) luaL_checknumber(L, 1));
            v.setY((float) luaL_checknumber(L, 2));
            v.setZ((float) luaL_checknumber(L, 3));
            v.setW((float) luaL_checknumber(L, 4));
        }
        PushVector4(L, v);
        return 1;
    }

    /*# creates a new identity quaternion
     *
     * Creates a new identity quaternion. The identity
     * quaternion is equal to:
     *
     * `vmath.quat(0, 0, 0, 1)`
     *
     * @name vmath.quat
     * @return q [type:quaternion] new identity quaternion
     * @examples
     *
     * ```lua
     * local quat = vmath.quat()
     * print(quat) --> vmath.quat(0, 0, 0, 1)
     * print(quat.w) --> 1
     * ```
     */

    /*# creates a new quaternion from another existing quaternion
     *
     * Creates a new quaternion with all components set to the
     * corresponding values from the supplied quaternion. I.e.
     * This function creates a copy of the given quaternion.
     *
     * @name vmath.quat
     * @param q1 [type:quaternion] existing quaternion
     * @return q [type:quaternion] new quaternion
     * @examples
     *
     * ```lua
     * local quat1 = vmath.quat(1, 2, 3, 4)
     * local quat2 = vmath.quat(quat1)
     * if quat1 == quat2 then
     *     -- yes, they are equal
     *     print(quat2) --> vmath.quat(1, 2, 3, 4)
     * end
     * ```
     */

    /*# creates a new quaternion from its coordinates
     *
     * Creates a new quaternion with the components set
     * according to the supplied parameter values.
     *
     * @name vmath.quat
     * @param x [type:number] x coordinate
     * @param y [type:number] y coordinate
     * @param z [type:number] z coordinate
     * @param w [type:number] w coordinate
     * @return q [type:quaternion] new quaternion
     * @examples
     *
     * ```lua
     * local quat = vmath.quat(1, 2, 3, 4)
     * print(quat) --> vmath.quat(1, 2, 3, 4)
     * ```
     */
    static int Quat_new(lua_State* L)
    {
        Vectormath::Aos::Quat q;
        if (lua_gettop(L) == 0)
        {
            q = Vectormath::Aos::Quat::identity();
        }
        else if (lua_gettop(L) == 1)
        {
            q = *CheckQuat(L, -1);
        }
        else
        {
            q.setX((float) luaL_checknumber(L, 1));
            q.setY((float) luaL_checknumber(L, 2));
            q.setZ((float) luaL_checknumber(L, 3));
            q.setW((float) luaL_checknumber(L, 4));
        }
        PushQuat(L, q);
        return 1;
    }

    /*# creates a quaternion to rotate between two unit vectors
     *
     * The resulting quaternion describes the rotation that,
     * if applied to the first vector, would rotate the first
     * vector to the second. The two vectors must be unit
     * vectors (of length 1).
     *
     * [icon:attention] The result is undefined if the two vectors point in opposite directions
     *
     * @name vmath.quat_from_to
     * @param v1 [type:vector3] first unit vector, before rotation
     * @param v2 [type:vector3] second unit vector, after rotation
     * @return q [type:quaternion] quaternion representing the rotation from first to second vector
     * @examples
     *
     * ```lua
     * local v1 = vmath.vector3(1, 0, 0)
     * local v2 = vmath.vector3(0, 1, 0)
     * local rot = vmath.quat_from_to(v1, v2)
     * print(vmath.rotate(rot, v1)) --> vmath.vector3(0, 0.99999994039536, 0)
     * ```
     */
    static int Quat_FromTo(lua_State* L)
    {
        Vectormath::Aos::Vector3* v1 = CheckVector3(L, 1);
        Vectormath::Aos::Vector3* v2 = CheckVector3(L, 2);
        PushQuat(L, Vectormath::Aos::Quat::rotation(*v1, *v2));
        return 1;
    }

    /*# creates a quaternion to rotate around a unit vector
     *
     * The resulting quaternion describes a rotation of `angle`
     * radians around the axis described by the unit vector `v`.
     *
     * @name vmath.quat_axis_angle
     * @param v [type:vector3] axis
     * @param angle [type:number] angle
     * @return q [type:quaternion] quaternion representing the axis-angle rotation
     * @examples
     *
     * ```lua
     * local axis = vmath.vector3(1, 0, 0)
     * local rot = vmath.quat_axis_angle(axis, 3.141592653)
     * local vec = vmath.vector3(1, 1, 0)
     * print(vmath.rotate(rot, vec)) --> vmath.vector3(1, -1, -8.7422776573476e-08)
     * ```
     */
    static int Quat_AxisAngle(lua_State* L)
    {
        Vectormath::Aos::Vector3* axis = CheckVector3(L, 1);
        float angle = (float) luaL_checknumber(L, 2);
        PushQuat(L, Vectormath::Aos::Quat::rotation(angle, *axis));
        return 1;
    }

    /*# creates a quaternion from three base unit vectors
     *
     * The resulting quaternion describes the rotation from the
     * identity quaternion (no rotation) to the coordinate system
     * as described by the given x, y and z base unit vectors.
     *
     * @name vmath.quat_basis
     * @param x [type:vector3] x base vector
     * @param y [type:vector3] y base vector
     * @param z [type:vector3] z base vector
     * @return q [type:quaternion] quaternion representing the rotation of the specified base vectors
     * @examples
     *
     * ```lua
     * -- Axis rotated 90 degrees around z.
     * local rot_x = vmath.vector3(0, -1, 0)
     * local rot_y = vmath.vector3(1, 0, 0)
     * local z = vmath.vector3(0, 0, 1)
     * local rot1 = vmath.quat_basis(rot_x, rot_y, z)
     * local rot2 = vmath.quat_from_to(vmath.vector3(0, 1, 0), vmath.vector3(1, 0, 0))
     * if rot1 == rot2 then
     *     -- These quaternions are equal!
     *     print(rot2) --> vmath.quat(0, 0, -0.70710676908493, 0.70710676908493)
     * end
     * ```
     */
    static int Quat_Basis(lua_State* L)
    {
        Vectormath::Aos::Vector3* x = CheckVector3(L, 1);
        Vectormath::Aos::Vector3* y = CheckVector3(L, 2);
        Vectormath::Aos::Vector3* z = CheckVector3(L, 3);
        Vectormath::Aos::Matrix3 m;
        m.setCol0(*x);
        m.setCol1(*y);
        m.setCol2(*z);
        PushQuat(L, Vectormath::Aos::Quat(m));
        return 1;
    }

    /*# creates a quaternion from rotation around x-axis
     *
     * The resulting quaternion describes a rotation of `angle`
     * radians around the x-axis.
     *
     * @name vmath.quat_rotation_x
     * @param angle [type:number] angle in radians around x-axis
     * @return q [type:quaternion] quaternion representing the rotation around the x-axis
     * @examples
     *
     * ```lua
     * local rot = vmath.quat_rotation_x(3.141592653)
     * local vec = vmath.vector3(1, 1, 0)
     * print(vmath.rotate(rot, vec)) --> vmath.vector3(1, -1, -8.7422776573476e-08)
     * ```
     */
    static int Quat_RotationX(lua_State* L)
    {
        float angle = (float) luaL_checknumber(L, 1);
        PushQuat(L, Vectormath::Aos::Quat::rotationX(angle));
        return 1;
    }

    /*# creates a quaternion from rotation around y-axis
     *
     * The resulting quaternion describes a rotation of `angle`
     * radians around the y-axis.
     *
     * @name vmath.quat_rotation_y
     * @param angle [type:number] angle in radians around y-axis
     * @return q [type:quaternion] quaternion representing the rotation around the y-axis
     * @examples
     *
     * ```lua
     * local rot = vmath.quat_rotation_y(3.141592653)
     * local vec = vmath.vector3(1, 1, 0)
     * print(vmath.rotate(rot, vec)) --> vmath.vector3(-1, 1, 8.7422776573476e-08)
     * ```
     */
    static int Quat_RotationY(lua_State* L)
    {
        float angle = (float) luaL_checknumber(L, 1);
        PushQuat(L, Vectormath::Aos::Quat::rotationY(angle));
        return 1;
    }

    /*# creates a quaternion from rotation around z-axis
     *
     * The resulting quaternion describes a rotation of `angle`
     * radians around the z-axis.
     *
     * @name vmath.quat_rotation_z
     * @param angle [type:number] angle in radians around z-axis
     * @return q [type:quaternion] quaternion representing the rotation around the z-axis
     * @examples
     *
     * ```lua
     * local rot = vmath.quat_rotation_z(3.141592653)
     * local vec = vmath.vector3(1, 1, 0)
     * print(vmath.rotate(rot, vec)) --> vmath.vector3(-0.99999988079071, -1, 0)
     * ```
     */
    static int Quat_RotationZ(lua_State* L)
    {
        float angle = (float) luaL_checknumber(L, 1);
        PushQuat(L, Vectormath::Aos::Quat::rotationZ(angle));
        return 1;
    }

    /*# creates a new identity matrix
     *
     * The resulting identity matrix describes a transform with
     * no translation or rotation.
     *
     * @name vmath.matrix4
     * @return m [type:matrix4] identity matrix
     * @examples
     *
     * ```lua
     * local mat = vmath.matrix4()
     * print(mat) --> vmath.matrix4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)
     * -- get column 0:
     * print(mat.c0) --> vmath.vector4(1, 0, 0, 0)
     * -- get the value in row 3 and column 2:
     * print(mat.m32) --> 0
     * ```
     */

    /*# creates a new matrix from another existing matrix
     *
     * Creates a new matrix with all components set to the
     * corresponding values from the supplied matrix. I.e.
     * the function creates a copy of the given matrix.
     *
     * @name vmath.matrix4
     * @param m1 [type:matrix4] existing matrix
     * @return m [type:matrix4] matrix which is a copy of the specified matrix
     * @examples
     *
     * ```lua
     * local mat1 = vmath.matrix4_rotation_x(3.141592653)
     * local mat2 = vmath.matrix4(mat1)
     * if mat1 == mat2 then
     *     -- yes, they are equal
     *     print(mat2) --> vmath.matrix4(1, 0, 0, 0, 0, -1, 8.7422776573476e-08, 0, 0, -8.7422776573476e-08, -1, 0, 0, 0, 0, 1)
     * end
     * ```
     */
    static int Matrix4_new(lua_State* L)
    {
        Vectormath::Aos::Matrix4 m;
        if (lua_gettop(L) == 0)
        {
            m = Vectormath::Aos::Matrix4::identity();
        }
        else if (lua_gettop(L) == 1)
        {
            m = *CheckMatrix4(L, -1);
        }
        else
        {
            return luaL_error(L, "A %s.%s can only be constructed with empty argument list or from another %s.", SCRIPT_LIB_NAME, SCRIPT_TYPE_NAME_MATRIX4, SCRIPT_TYPE_NAME_MATRIX4);
        }
        PushMatrix4(L, m);
        return 1;
    }

    /*# creates a frustum matrix
     *
     * Constructs a frustum matrix from the given values. The left, right,
     * top and bottom coordinates of the view cone are expressed as distances
     * from the center of the near clipping plane. The near and far coordinates
     * are expressed as distances from the tip of the view frustum cone.
     *
     * @name vmath.matrix4_frustum
     * @param left [type:number] coordinate for left clipping plane
     * @param right [type:number] coordinate for right clipping plane
     * @param bottom [type:number] coordinate for bottom clipping plane
     * @param top [type:number] coordinate for top clipping plane
     * @param near [type:number] coordinate for near clipping plane
     * @param far [type:number] coordinate for far clipping plane
     * @return m [type:matrix4] matrix representing the frustum
     * @examples
     *
     * ```lua
     * -- Construct a projection frustum with a vertical and horizontal
     * -- FOV of 45 degrees. Useful for rendering a square view.
     * local proj = vmath.matrix4_frustum(-1, 1, -1, 1, 1, 1000)
     * render.set_projection(proj)
     * ```
     */
    static int Matrix4_Frustum(lua_State* L)
    {
        float left = (float) luaL_checknumber(L, 1);
        float right = (float) luaL_checknumber(L, 2);
        float bottom = (float) luaL_checknumber(L, 3);
        float top = (float) luaL_checknumber(L, 4);
        float near_z = (float) luaL_checknumber(L, 5);
        if(near_z == 0.0f)
        {
            luaL_where(L, 1);
            dmLogWarning("%sperspective projection invalid, znear = 0", lua_tostring(L,-1));
        }
        float far_z = (float) luaL_checknumber(L, 6);
        PushMatrix4(L, Vectormath::Aos::Matrix4::frustum(left, right, bottom, top, near_z, far_z));
        return 1;
    }

    /*# creates a look-at view matrix
     *
     * The resulting matrix is created from the supplied look-at parameters.
     * This is useful for constructing a view matrix for a camera or
     * rendering in general.
     *
     * @name vmath.matrix4_look_at
     * @param eye [type:vector3] eye position
     * @param look_at [type:vector3] look-at position
     * @param up [type:vector3] up vector
     * @return m [type:matrix4] look-at matrix
     * @examples
     *
     * ```lua
     * -- Set up a perspective camera at z 100 with 45 degrees (pi/2) FOV
     * -- Aspect ratio 4:3
     * local eye = vmath.vector3(0, 0, 100)
     * local look_at = vmath.vector3(0, 0, 0)
     * local up = vmath.vector3(0, 1, 0)
     * local view = vmath.matrix4_look_at(eye, look_at, up)
     * render.set_view(view)
     * local proj = vmath.matrix4_perspective(3.141592/2, 4/3, 1, 1000)
     * render.set_projection(proj)
     * ```
     */
    static int Matrix4_LookAt(lua_State* L)
    {
        PushMatrix4(L, Vectormath::Aos::Matrix4::lookAt(Vectormath::Aos::Point3(*CheckVector3(L, 1)), Vectormath::Aos::Point3(*CheckVector3(L, 2)), *CheckVector3(L, 3)));
        return 1;
    }

    /*# creates an orthographic projection matrix
     * Creates an orthographic projection matrix.
     * This is useful to construct a projection matrix for a camera or rendering in general.
     *
     * @name vmath.matrix4_orthographic
     * @param left [type:number] coordinate for left clipping plane
     * @param right [type:number] coordinate for right clipping plane
     * @param bottom [type:number] coordinate for bottom clipping plane
     * @param top [type:number] coordinate for top clipping plane
     * @param near [type:number] coordinate for near clipping plane
     * @param far [type:number] coordinate for far clipping plane
     * @return m [type:matrix4] orthographic projection matrix
     * @examples
     *
     * ```lua
     * -- Set up an orthographic projection based on the width and height
     * -- of the game window.
     * local w = render.get_width()
     * local h = render.get_height()
     * local proj = vmath.matrix4_orthographic(- w / 2, w / 2, -h / 2, h / 2, -1000, 1000)
     * render.set_projection(proj)
     * ```
     */
    static int Matrix4_Orthographic(lua_State* L)
    {
        float left = (float) luaL_checknumber(L, 1);
        float right = (float) luaL_checknumber(L, 2);
        float bottom = (float) luaL_checknumber(L, 3);
        float top = (float) luaL_checknumber(L, 4);
        float near_z = (float) luaL_checknumber(L, 5);
        float far_z = (float) luaL_checknumber(L, 6);
        PushMatrix4(L, Vectormath::Aos::Matrix4::orthographic(left, right, bottom, top, near_z, far_z));
        return 1;
    }

    /*# creates a perspective projection matrix
     * Creates a perspective projection matrix.
     * This is useful to construct a projection matrix for a camera or rendering in general.
     *
     * @name vmath.matrix4_perspective
     * @param fov [type:number] angle of the full vertical field of view in radians
     * @param aspect [type:number] aspect ratio
     * @param near [type:number] coordinate for near clipping plane
     * @param far [type:number] coordinate for far clipping plane
     * @return m [type:matrix4] perspective projection matrix
     * @examples
     *
     * ```lua
     * -- Set up a perspective camera at z 100 with 45 degrees (pi/2) FOV
     * -- Aspect ratio 4:3
     * local eye = vmath.vector3(0, 0, 100)
     * local look_at = vmath.vector3(0, 0, 0)
     * local up = vmath.vector3(0, 1, 0)
     * local view = vmath.matrix4_look_at(eye, look_at, up)
     * render.set_view(view)
     * local proj = vmath.matrix4_perspective(3.141592/2, 4/3, 1, 1000)
     * render.set_projection(proj)
     * ```
     */
    static int Matrix4_Perspective(lua_State* L)
    {
        float fov = (float) luaL_checknumber(L, 1);
        float aspect = (float) luaL_checknumber(L, 2);
        float near_z = (float) luaL_checknumber(L, 3);
        float far_z = (float) luaL_checknumber(L, 4);
        if(near_z == 0.0f)
        {
            luaL_where(L, 1);
            dmLogWarning("%sperspective projection invalid, znear = 0", lua_tostring(L,-1));
        }
        PushMatrix4(L, Vectormath::Aos::Matrix4::perspective(fov, aspect, near_z, far_z));
        return 1;
    }

    /*# creates a matrix from a quaternion
     * The resulting matrix describes the same rotation as the quaternion, but does not have any translation (also like the quaternion).
     *
     * @name vmath.matrix4_from_quat
     * @param q [type:quaternion] quaternion to create matrix from
     * @return m [type:matrix4] matrix represented by quaternion
     * @examples
     *
     * ```lua
     * local vec = vmath.vector4(1, 1, 0, 0)
     * local quat = vmath.quat_rotation_z(3.141592653)
     * local mat = vmath.matrix4_from_quat(quat)
     * print(mat * vec) --> vmath.matrix4_frustum(-1, 1, -1, 1, 1, 1000)
     * ```
     */
    static int Matrix4_FromQuat(lua_State* L)
    {
        PushMatrix4(L, Vectormath::Aos::Matrix4::rotation(*CheckQuat(L, 1)));
        return 1;
    }

    /*# creates a matrix from an axis and an angle
     * The resulting matrix describes a rotation around the axis by the specified angle.
     *
     * @name vmath.matrix4_axis_angle
     * @param v [type:vector3] axis
     * @param angle [type:number] angle in radians
     * @return m [type:matrix4] matrix represented by axis and angle
     * @examples
     *
     * ```lua
     * local vec = vmath.vector4(1, 1, 0, 0)
     * local axis = vmath.vector3(0, 0, 1) -- z-axis
     * local mat = vmath.matrix4_axis_angle(axis, 3.141592653)
     * print(mat * vec) --> vmath.vector4(-0.99999994039536, -1.0000001192093, 0, 0)
     * ```
     */
    static int Matrix4_AxisAngle(lua_State* L)
    {
        Vectormath::Aos::Vector3* axis = CheckVector3(L, 1);
        float angle = (float) luaL_checknumber(L, 2);
        PushMatrix4(L, Vectormath::Aos::Matrix4::rotation(angle, *axis));
        return 1;
    }

    /*# creates a matrix from rotation around x-axis
     * The resulting matrix describes a rotation around the x-axis
     * by the specified angle.
     *
     * @name vmath.matrix4_rotation_x
     * @param angle [type:number] angle in radians around x-axis
     * @return m [type:matrix4] matrix from rotation around x-axis
     * @examples
     *
     * ```lua
     * local vec = vmath.vector4(1, 1, 0, 0)
     * local mat = vmath.matrix4_rotation_x(3.141592653)
     * print(mat * vec) --> vmath.vector4(1, -1, -8.7422776573476e-08, 0)
     * ```
     */
    static int Matrix4_RotationX(lua_State* L)
    {
        PushMatrix4(L, Vectormath::Aos::Matrix4::rotationX((float) luaL_checknumber(L, 1)));
        return 1;
    }

    /*# creates a matrix from rotation around y-axis
     * The resulting matrix describes a rotation around the y-axis
     * by the specified angle.
     *
     * @name vmath.matrix4_rotation_y
     * @param angle [type:number] angle in radians around y-axis
     * @return m [type:matrix4] matrix from rotation around y-axis
     * @examples
     *
     * ```lua
     * local vec = vmath.vector4(1, 1, 0, 0)
     * local mat = vmath.matrix4_rotation_y(3.141592653)
     * print(mat * vec) --> vmath.vector4(-1, 1, 8.7422776573476e-08, 0)
     * ```
     */
    static int Matrix4_RotationY(lua_State* L)
    {
        PushMatrix4(L, Vectormath::Aos::Matrix4::rotationY((float) luaL_checknumber(L, 1)));
        return 1;
    }

    /*# creates a matrix from rotation around z-axis
     * The resulting matrix describes a rotation around the z-axis
     * by the specified angle.
     *
     * @name vmath.matrix4_rotation_z
     * @param angle [type:number] angle in radians around z-axis
     * @return m [type:matrix4] matrix from rotation around z-axis
     * @examples
     *
     * ```lua
     * local vec = vmath.vector4(1, 1, 0, 0)
     * local mat = vmath.matrix4_rotation_z(3.141592653)
     * print(mat * vec) --> vmath.vector4(-0.99999994039536, -1.0000001192093, 0, 0)
     * ```
     */
    static int Matrix4_RotationZ(lua_State* L)
    {
        PushMatrix4(L, Vectormath::Aos::Matrix4::rotationZ((float) luaL_checknumber(L, 1)));
        return 1;
    }

    /*# creates a translation matrix from a position vector
     * The resulting matrix describes a translation of a point
     * in euclidean space.
     *
     * @name vmath.matrix4_translation
     * @param position [type:vector3|vector4] position vector to create matrix from
     * @return m [type:matrix4] matrix from the supplied position vector
     * @examples
     *
     * ```lua
     * -- Set camera view from custom view and translation matrices
     * local mat_trans = vmath.matrix4_translation(vmath.vector3(0, 10, 100))
     * local mat_view  = vmath.matrix4_rotation_y(-3.141592/4)
     * render.set_view(mat_view * mat_trans)
     * ```
     */
    static int Matrix4_Translation(lua_State* L)
    {
        const ScriptUserType v_type = GetType(L, 1);

        if (v_type == SCRIPT_TYPE_VECTOR3)
        {
            const Vectormath::Aos::Vector3* t1 = CheckVector3(L, 1);
            PushMatrix4(L, Vectormath::Aos::Matrix4::translation(*t1));
        }
        else if (v_type == SCRIPT_TYPE_VECTOR4)
        {
            const Vectormath::Aos::Vector4* t1 = CheckVector4(L, 1);
            PushMatrix4(L, Vectormath::Aos::Matrix4::translation(t1->getXYZ()));
        }
        else
        {
            return luaL_error(L, "%s.%s accepts (%s|%s) as arguments.", SCRIPT_LIB_NAME, "matrix4_translation",
                SCRIPT_TYPE_NAME_VECTOR3, SCRIPT_TYPE_NAME_VECTOR4);
        }

        return 1;
    }

    /*# calculates the inverse matrix.
     * The resulting matrix is the inverse of the supplied matrix.
     *
     * [icon:attention] For ortho-normal matrices, e.g. regular object transformation,
     * use `vmath.ortho_inv()` instead.
     * The specialized inverse for ortho-normalized matrices is much faster
     * than the general inverse.
     *
     * @name vmath.inv
     * @param m1 [type:matrix4] matrix to invert
     * @return m [type:matrix4] inverse of the supplied matrix
     * @examples
     *
     * ```lua
     * local mat1 = vmath.matrix4_rotation_z(3.141592653)
     * local mat2 = vmath.inv(mat1)
     * -- M * inv(M) = identity matrix
     * print(mat1 * mat2) --> vmath.matrix4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)
     * ```
     */
    static int Inverse(lua_State* L)
    {
        const Vectormath::Aos::Matrix4* m = CheckMatrix4(L, 1);
        Vectormath::Aos::Matrix4 mi = Vectormath::Aos::inverse(*m);
        PushMatrix4(L, mi);
        return 1;
    }

    /*# calculates the inverse of an ortho-normal matrix.
     * The resulting matrix is the inverse of the supplied matrix.
     * The supplied matrix has to be an ortho-normal matrix, e.g.
     * describe a regular object transformation.
     *
     * [icon:attention] For matrices that are not ortho-normal
     * use the general inverse `vmath.inv()` instead.
     *
     * @name vmath.ortho_inv
     * @param m1 [type:matrix4] ortho-normalized matrix to invert
     * @return m [type:matrix4] inverse of the supplied matrix
     * @examples
     *
     * ```lua
     * local mat1 = vmath.matrix4_rotation_z(3.141592653)
     * local mat2 = vmath.ortho_inv(mat1)
     * -- M * inv(M) = identity matrix
     * print(mat1 * mat2) --> vmath.matrix4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)
     * ```
     */
    static int OrthoInverse(lua_State* L)
    {
        const Vectormath::Aos::Matrix4* m = CheckMatrix4(L, 1);
        Vectormath::Aos::Matrix4 mi = Vectormath::Aos::orthoInverse(*m);
        PushMatrix4(L, mi);
        return 1;
    }

    /*# calculates the dot-product of two vectors
     *
     * The returned value is a scalar defined as:
     *
     * <code>P ⋅ Q = |P| |Q| cos θ</code>
     *
     * where θ is the angle between the vectors P and Q.
     *
     * - If the dot product is positive then the angle between the vectors is below 90 degrees.
     * - If the dot product is zero the vectors are perpendicular (at right-angles to each other).
     * - If the dot product is negative then the angle between the vectors is more than 90 degrees.
     *
     * @name vmath.dot
     * @param v1 [type:vector3|vector4] first vector
     * @param v2 [type:vector3|vector4] second vector
     * @return n [type:number] dot product
     * @examples
     *
     * ```lua
     * if vmath.dot(vector1, vector2) == 0 then
     *     -- The two vectors are perpendicular (at right-angles to each other)
     *     ...
     * end
     * ```
     */
    static int Dot(lua_State* L)
    {
        const ScriptUserType type1 = GetType(L, 1);
        const ScriptUserType type2 = GetType(L, 2);

        if (type1 != type2)
        {
            return luaL_error(L, "%s.%s Arguments needs to be of same type!", SCRIPT_LIB_NAME, "dot");
        }
        if (type1 == SCRIPT_TYPE_VECTOR3 && type2 == SCRIPT_TYPE_VECTOR3)
        {
            Vectormath::Aos::Vector3* v1 = CheckVector3(L, 1);
            Vectormath::Aos::Vector3* v2 = CheckVector3(L, 2);
            lua_pushnumber(L, Vectormath::Aos::dot(*v1, *v2));
        }
        else if (type1 == SCRIPT_TYPE_VECTOR4 && type2 == SCRIPT_TYPE_VECTOR4)
        {
            Vectormath::Aos::Vector4* v1 = CheckVector4(L, 1);
            Vectormath::Aos::Vector4* v2 = CheckVector4(L, 2);
            lua_pushnumber(L, Vectormath::Aos::dot(*v1, *v2));
        }
        else
        {
            return luaL_error(L, "%s.%s accepts (%s|%s) as arguments.", SCRIPT_LIB_NAME, "dot", SCRIPT_TYPE_NAME_VECTOR3, SCRIPT_TYPE_NAME_VECTOR4);
        }
        return 1;
    }

    /*# calculates the squared length of a vector or quaternion
     *
     * Returns the squared length of the supplied vector or quaternion.
     *
     * @name vmath.length_sqr
     * @param v [type:vector3|vector4|quat] value of which to calculate the squared length
     * @return n [type:number] squared length
     * @examples
     *
     * ```lua
     * if vmath.length_sqr(vector1) < vmath.length_sqr(vector2) then
     *     -- Vector 1 has less magnitude than vector 2
     *     ...
     * end
     * ```
     */
    static int LengthSqr(lua_State* L)
    {
        const ScriptUserType type = GetType(L, 1);
        if (type == SCRIPT_TYPE_VECTOR3)
        {
            Vectormath::Aos::Vector3* v = CheckVector3(L, 1);
            lua_pushnumber(L, Vectormath::Aos::lengthSqr(*v));
        }
        else if (type == SCRIPT_TYPE_VECTOR4)
        {
            Vectormath::Aos::Vector4* v = CheckVector4(L, 1);
            lua_pushnumber(L, Vectormath::Aos::lengthSqr(*v));
        }
        else if (type == SCRIPT_TYPE_QUAT)
        {
            Vectormath::Aos::Quat* value = CheckQuat(L, 1);
            // quat doesn't have a lengthSqr(), but this is what's called before the sqrtf in length()
            lua_pushnumber(L, Vectormath::Aos::norm(*value));
        }
        else
        {
            return luaL_error(L, "%s.%s accepts (%s|%s|%s) as argument.", SCRIPT_LIB_NAME, "lengthSqr", SCRIPT_TYPE_NAME_VECTOR3, SCRIPT_TYPE_NAME_VECTOR4, SCRIPT_TYPE_NAME_QUAT);
        }
        return 1;
    }

    /*# calculates the length of a vector or quaternion
     *
     * Returns the length of the supplied vector or quaternion.
     *
     * If you are comparing the lengths of vectors or quaternions, you should compare
     * the length squared instead as it is slightly more efficient to calculate
     * (it eliminates a square root calculation).
     *
     * @name vmath.length
     * @param v [type:vector3|vector4|quat] value of which to calculate the length
     * @return n [type:number] length
     * @examples
     *
     * ```lua
     * if vmath.length(self.velocity) < max_velocity then
     *     -- The speed (velocity vector) is below max.
     *
     *     -- TODO: max_velocity can be expressed as squared
     *     -- so we can compare with length_sqr() instead.
     *     ...
     * end
     * ```
     */
    static int Length(lua_State* L)
    {
        const ScriptUserType type = GetType(L, 1);
        if (type == SCRIPT_TYPE_VECTOR3)
        {
            Vectormath::Aos::Vector3* v = CheckVector3(L, 1);
            lua_pushnumber(L, Vectormath::Aos::length(*v));
        }
        else if (type == SCRIPT_TYPE_VECTOR4)
        {
            Vectormath::Aos::Vector4* v = CheckVector4(L, 1);
            lua_pushnumber(L, Vectormath::Aos::length(*v));
        }
        else if (type == SCRIPT_TYPE_QUAT)
        {
            Vectormath::Aos::Quat* value = CheckQuat(L, 1);
            lua_pushnumber(L, Vectormath::Aos::length(*value));
        }
        else
        {
            return luaL_error(L, "%s.%s accepts (%s|%s|%s) as argument.", SCRIPT_LIB_NAME, "length", SCRIPT_TYPE_NAME_VECTOR3, SCRIPT_TYPE_NAME_VECTOR4, SCRIPT_TYPE_NAME_QUAT);
        }
        return 1;
    }

    /*# normalizes a vector
     *
     * Normalizes a vector, i.e. returns a new vector with the same
     * direction as the input vector, but with length 1.
     *
     * [icon:attention] The length of the vector must be above 0, otherwise a
     * division-by-zero will occur.
     *
     * @name vmath.normalize
     * @param v1 [type:vector3|vector4|quat] vector to normalize
     * @return v [type:vector3|vector4|quat] new normalized vector
     * @examples
     *
     * ```lua
     * local vec = vmath.vector3(1, 2, 3)
     * local norm_vec = vmath.normalize(vec)
     * print(norm_vec) --> vmath.vector3(0.26726123690605, 0.5345224738121, 0.80178368091583)
     * print(vmath.length(norm_vec)) --> 0.99999994039536
     * ```
     */
    static int Normalize(lua_State* L)
    {
        const ScriptUserType type = GetType(L, 1);
        if (type == SCRIPT_TYPE_VECTOR3)
        {
            Vectormath::Aos::Vector3* v = CheckVector3(L, 1);
            PushVector3(L, Vectormath::Aos::normalize(*v));
        }
        else if (type == SCRIPT_TYPE_VECTOR4)
        {
            Vectormath::Aos::Vector4* v = CheckVector4(L, 1);
            PushVector4(L, Vectormath::Aos::normalize(*v));
        }
        else if (type == SCRIPT_TYPE_QUAT)
        {
            Vectormath::Aos::Quat* value = CheckQuat(L, 1);
            PushQuat(L, Vectormath::Aos::normalize(*value));
        }
        else
        {
            return luaL_error(L, "%s.%s accepts (%s|%s|%s) as argument.", SCRIPT_LIB_NAME, "normalize", SCRIPT_TYPE_NAME_VECTOR3, SCRIPT_TYPE_NAME_VECTOR4, SCRIPT_TYPE_NAME_QUAT);
        }
        return 1;
    }

    /*# calculates the cross-product of two vectors
     *
     * Given two linearly independent vectors P and Q, the cross product,
     * P × Q, is a vector that is perpendicular to both P and Q and
     * therefore normal to the plane containing them.
     *
     * If the two vectors have the same direction (or have the exact
     * opposite direction from one another, i.e. are not linearly independent)
     * or if either one has zero length, then their cross product is zero.
     *
     * @name vmath.cross
     * @param v1 [type:vector3] first vector
     * @param v2 [type:vector3] second vector
     * @return v [type:vector3] a new vector representing the cross product
     * @examples
     *
     * ```lua
     * local vec1 = vmath.vector3(1, 0, 0)
     * local vec2 = vmath.vector3(0, 1, 0)
     * print(vmath.cross(vec1, vec2)) --> vmath.vector3(0, 0, 1)
     * local vec3 = vmath.vector3(-1, 0, 0)
     * print(vmath.cross(vec1, vec3)) --> vmath.vector3(0, -0, 0)
     * ```
     */
    static int Cross(lua_State* L)
    {
        Vectormath::Aos::Vector3* v1 = CheckVector3(L, 1);
        Vectormath::Aos::Vector3* v2 = CheckVector3(L, 2);
        PushVector3(L, Vectormath::Aos::cross(*v1, *v2));
        return 1;
    }

    /*# lerps between two vectors
     *
     * Linearly interpolate between two vectors. The function
     * treats the vectors as positions and interpolates between
     * the positions in a straight line. Lerp is useful to describe
     * transitions from one place to another over time.
     *
     * [icon:attention] The function does not clamp t between 0 and 1.
     *
     * @name vmath.lerp
     * @param t [type:number] interpolation parameter, 0-1
     * @param v1 [type:vector3|vector4] vector to lerp from
     * @param v2 [type:vector3|vector4] vector to lerp to
     * @return v [type:vector3|vector4] the lerped vector
     * @examples
     *
     * ```lua
     * function init(self)
     *     self.t = 0
     * end
     *
     * function update(self, dt)
     *     self.t = self.t + dt
     *     if self.t <= 1 then
     *         local startpos = vmath.vector3(0, 600, 0)
     *         local endpos = vmath.vector3(600, 0, 0)
     *         local pos = vmath.lerp(self.t, startpos, endpos)
     *         go.set_position(pos, "go")
     *     end
     * end
     * ```
     */

    /*# lerps between two quaternions
     *
     * Linearly interpolate between two quaternions. Linear
     * interpolation of rotations are only useful for small
     * rotations. For interpolations of arbitrary rotations,
     * [ref:vmath.slerp] yields much better results.
     *
     * [icon:attention] The function does not clamp t between 0 and 1.
     *
     * @name vmath.lerp
     * @param t [type:number] interpolation parameter, 0-1
     * @param q1 [type:quaternion] quaternion to lerp from
     * @param q2 [type:quaternion] quaternion to lerp to
     * @return q [type:quaternion] the lerped quaternion
     * @examples
     *
     * ```lua
     * function init(self)
     *     self.t = 0
     * end
     *
     * function update(self, dt)
     *     self.t = self.t + dt
     *     if self.t <= 1 then
     *         local startrot = vmath.quat_rotation_z(0)
     *         local endrot = vmath.quat_rotation_z(3.141592653)
     *         local rot = vmath.lerp(self.t, startrot, endrot)
     *         go.set_rotation(rot, "go")
     *     end
     * end
     * ```
     */

    /*# lerps between two numbers
     *
     * Linearly interpolate between two values. Lerp is useful
     * to describe transitions from one value to another over time.
     *
     * [icon:attention] The function does not clamp t between 0 and 1.
     *
     * @name vmath.lerp
     * @param t [type:number] interpolation parameter, 0-1
     * @param n1 [type:number] number to lerp from
     * @param n2 [type:number] number to lerp to
     * @return n [type:number] the lerped number
     * @examples
     *
     * ```lua
     * function init(self)
     *     self.t = 0
     * end
     *
     * function update(self, dt)
     *     self.t = self.t + dt
     *     if self.t <= 1 then
     *         local startx = 0
     *         local endx = 600
     *         local x = vmath.lerp(self.t, startx, endx)
     *         go.set_position(vmath.vector3(x, 100, 0), "go")
     *     end
     * end
     * ```
     */

    static int Lerp(lua_State* L)
    {
        const ScriptUserType type1 = GetType(L, 2);
        const ScriptUserType type2 = GetType(L, 3);
        float t = (float) luaL_checknumber(L, 1);
        if (type1 == type2 && type1 != SCRIPT_TYPE_UNKNOWN)
        {
            if (type1 == SCRIPT_TYPE_VECTOR3 && type2 == SCRIPT_TYPE_VECTOR3)
            {
                Vectormath::Aos::Vector3* v1 = CheckVector3(L, 2);
                Vectormath::Aos::Vector3* v2 = CheckVector3(L, 3);
                PushVector3(L, Vectormath::Aos::lerp(t, *v1, *v2));
                return 1;
            }
            else if (type1 == SCRIPT_TYPE_VECTOR4 && type2 == SCRIPT_TYPE_VECTOR4)
            {
                Vectormath::Aos::Vector4* v1 = CheckVector4(L, 2);
                Vectormath::Aos::Vector4* v2 = CheckVector4(L, 3);
                PushVector4(L, Vectormath::Aos::lerp(t, *v1, *v2));
                return 1;
            }
            else if (type1 == SCRIPT_TYPE_QUAT && type2 == SCRIPT_TYPE_QUAT)
            {
                Vectormath::Aos::Quat* q1 = CheckQuat(L, 2);
                Vectormath::Aos::Quat* q2 = CheckQuat(L, 3);
                PushQuat(L, Vectormath::Aos::lerp(t, *q1, *q2));
                return 1;
            }
        }
        else if (lua_isnumber(L, 2) && lua_isnumber(L, 3))
        {
            lua_Number n1 = (float) luaL_checknumber(L, 2);
            lua_Number n2 = (float) luaL_checknumber(L, 3);
            lua_pushnumber(L, n1 + t * (n2 - n1));
            return 1;
        }
        return luaL_error(L, "%s.%s takes one number and a pair of either %s.%ss, %s.%ss, %s.%ss or numbers as arguments.", SCRIPT_LIB_NAME, "lerp", SCRIPT_LIB_NAME, SCRIPT_TYPE_NAME_VECTOR3, SCRIPT_LIB_NAME, SCRIPT_TYPE_NAME_VECTOR4, SCRIPT_LIB_NAME, SCRIPT_TYPE_NAME_QUAT);
    }

    /*# slerps between two vectors
     *
     * Spherically interpolates between two vectors. The difference to
     * lerp is that slerp treats the vectors as directions instead of
     * positions in space.
     *
     * The direction of the returned vector is interpolated by the angle
     * and the magnitude is interpolated between the magnitudes of the
     * from and to vectors.
     *
     * [icon:attention] Slerp is computationally more expensive than lerp.
     * The function does not clamp t between 0 and 1.
     *
     * @name vmath.slerp
     * @param t [type:number] interpolation parameter, 0-1
     * @param v1 [type:vector3|vector4] vector to slerp from
     * @param v2 [type:vector3|vector4] vector to slerp to
     * @return v [type:vector3|vector4] the slerped vector
     * @examples
     *
     * ```lua
     * function init(self)
     *     self.t = 0
     * end
     *
     * function update(self, dt)
     *     self.t = self.t + dt
     *     if self.t <= 1 then
     *         local startpos = vmath.vector3(0, 600, 0)
     *         local endpos = vmath.vector3(600, 0, 0)
     *         local pos = vmath.slerp(self.t, startpos, endpos)
     *         go.set_position(pos, "go")
     *     end
     * end
     * ```
     */

    /*# slerps between two quaternions
     *
     * Slerp travels the torque-minimal path maintaining constant
     * velocity, which means it travels along the straightest path along
     * the rounded surface of a sphere. Slerp is useful for interpolation
     * of rotations.
     *
     * Slerp travels the torque-minimal path, which means it travels
     * along the straightest path the rounded surface of a sphere.
     *
     * [icon:attention] The function does not clamp t between 0 and 1.
     *
     * @name vmath.slerp
     * @param t [type:number] interpolation parameter, 0-1
     * @param q1 [type:quaternion] quaternion to slerp from
     * @param q2 [type:quaternion] quaternion to slerp to
     * @return q [type:quaternion] the slerped quaternion
     * @examples
     *
     * ```lua
     * function init(self)
     *     self.t = 0
     * end
     *
     * function update(self, dt)
     *     self.t = self.t + dt
     *     if self.t <= 1 then
     *         local startrot = vmath.quat_rotation_z(0)
     *         local endrot = vmath.quat_rotation_z(3.141592653)
     *         local rot = vmath.slerp(self.t, startrot, endrot)
     *         go.set_rotation(rot, "go")
     *     end
     * end
     * ```
     */
    static int Slerp(lua_State* L)
    {
        const ScriptUserType type1 = GetType(L, 2);
        const ScriptUserType type2 = GetType(L, 3);

        if (type1 == type2)
        {
            float t = (float) luaL_checknumber(L, 1);
            if (type1 == SCRIPT_TYPE_QUAT && type2 == SCRIPT_TYPE_QUAT)
            {
                Vectormath::Aos::Quat* q1 = (Vectormath::Aos::Quat*)lua_touserdata(L, 2);
                Vectormath::Aos::Quat* q2 = (Vectormath::Aos::Quat*)lua_touserdata(L, 3);
                PushQuat(L, Vectormath::Aos::slerp(t, *q1, *q2));
                return 1;
            }
            else if (type1 == SCRIPT_TYPE_VECTOR4 && type2 == SCRIPT_TYPE_VECTOR4)
            {
                Vectormath::Aos::Vector4* v1 = CheckVector4(L, 2);
                Vectormath::Aos::Vector4* v2 = CheckVector4(L, 3);
                PushVector4(L, Vectormath::Aos::slerp(t, *v1, *v2));
                return 1;
            }
            else if (type1 == SCRIPT_TYPE_VECTOR3 && type2 == SCRIPT_TYPE_VECTOR3)
            {
                Vectormath::Aos::Vector3* v1 = CheckVector3(L, 2);
                Vectormath::Aos::Vector3* v2 = CheckVector3(L, 3);
                PushVector3(L, Vectormath::Aos::slerp(t, *v1, *v2));
                return 1;
            }
        }
        return luaL_error(L, "%s.%s takes one number and either two %s.%s or two %s.%s as arguments.", SCRIPT_LIB_NAME, "slerp", SCRIPT_LIB_NAME, SCRIPT_TYPE_NAME_VECTOR3, SCRIPT_LIB_NAME, SCRIPT_TYPE_NAME_QUAT);
    }

    /*# calculates the conjugate of a quaternion
     *
     * Calculates the conjugate of a quaternion. The result is a
     * quaternion with the same magnitudes but with the sign of
     * the imaginary (vector) parts changed:
     *
     * <code>q<super>*</super> = [w, -v]</code>
     *
     * @name vmath.conj
     * @param q1 [type:quaternion] quaternion of which to calculate the conjugate
     * @return q [type:quaternion] the conjugate
     * @examples
     *
     * ```lua
     * local quat = vmath.quat(1, 2, 3, 4)
     * print(vmath.conj(quat)) --> vmath.quat(-1, -2, -3, 4)
     * ```
     */
    static int Conj(lua_State* L)
    {
        Vectormath::Aos::Quat* q = CheckQuat(L, 1);
        PushQuat(L, Vectormath::Aos::conj(*q));
        return 1;
    }

    /*# rotates a vector by a quaternion
     *
     * Returns a new vector from the supplied vector that is
     * rotated by the rotation described by the supplied
     * quaternion.
     *
     * @name vmath.rotate
     * @param q [type:quaternion] quaternion
     * @param v1 [type:vector3] vector to rotate
     * @return v [type:vector3] the rotated vector
     * @examples
     *
     * ```lua
     * local vec = vmath.vector3(1, 1, 0)
     * local rot = vmath.quat_rotation_z(3.141592563)
     * print(vmath.rotate(rot, vec)) --> vmath.vector3(-1.0000002384186, -0.99999988079071, 0)
     * ```
     */
    static int Rotate(lua_State* L)
    {
        Vectormath::Aos::Quat* q = CheckQuat(L, 1);
        Vectormath::Aos::Vector3* v = CheckVector3(L, 2);
        PushVector3(L, Vectormath::Aos::rotate(*q, *v));
        return 1;
    }

    /*# projects a vector onto another vector
     *
     * Calculates the extent the projection of the first vector onto the second.
     * The returned value is a scalar p defined as:
     *
     * <code>p = |P| cos θ / |Q|</code>
     *
     * where θ is the angle between the vectors P and Q.
     *
     * @name vmath.project
     * @param v1 [type:vector3] vector to be projected on the second
     * @param v2 [type:vector3] vector onto which the first will be projected, must not have zero length
     * @return n [type:number] the projected extent of the first vector onto the second
     * @examples
     *
     * ```lua
     * local v1 = vmath.vector3(1, 1, 0)
     * local v2 = vmath.vector3(2, 0, 0)
     * print(vmath.project(v1, v2)) --> 0.5
     * ```
     */
    static int Project(lua_State* L)
    {
        Vectormath::Aos::Vector3* v1 = CheckVector3(L, 1);
        Vectormath::Aos::Vector3* v2 = CheckVector3(L, 2);
        float sq_len = Vectormath::Aos::lengthSqr(*v2);
        if (sq_len == 0.0f)
            return luaL_error(L, "The second %s.%s to %s.%s must have a length bigger than 0.", SCRIPT_LIB_NAME, SCRIPT_TYPE_NAME_VECTOR3, SCRIPT_LIB_NAME, "project");
        lua_pushnumber(L, Vectormath::Aos::dot(*v1, *v2) / sq_len);
        return 1;
    }

    /*# performs an element wise multiplication of two vectors
     *
     * Performs an element wise multiplication between two vectors of the same type
     * The returned value is a vector defined as (e.g. for a vector3):
     *
     * <code>v = vmath.mul_per_elem(a, b) = vmath.vector3(a.x * b.x, a.y * b.y, a.z * b.z)</code>
     *
     * @name vmath.mul_per_elem
     * @param v1 [type:vector3|vector4] first vector
     * @param v2 [type:vector3|vector4] second vector
     * @return v [type:vector3|vector4] multiplied vector
     * @examples
     *
     * ```lua
     * local blend_color = vmath.mul_per_elem(color1, color2)
     * ```
     */
    static int MulPerElem(lua_State* L)
    {
        const ScriptUserType type1 = GetType(L, 1);
        const ScriptUserType type2 = GetType(L, 2);

        if (type1 != type2)
        {
            return luaL_error(L, "%s.%s Arguments needs to be of same type!", SCRIPT_LIB_NAME, "mul_per_elem");
        }
        if (type1 == SCRIPT_TYPE_VECTOR3 && type2 == SCRIPT_TYPE_VECTOR3)
        {
            Vectormath::Aos::Vector3* v1 = CheckVector3(L, 1);
            Vectormath::Aos::Vector3* v2 = CheckVector3(L, 2);
            PushVector3(L, Vectormath::Aos::mulPerElem(*v1, *v2));
        }
        else if (type1 == SCRIPT_TYPE_VECTOR4 && type2 == SCRIPT_TYPE_VECTOR4)
        {
            Vectormath::Aos::Vector4* v1 = CheckVector4(L, 1);
            Vectormath::Aos::Vector4* v2 = CheckVector4(L, 2);
            PushVector4(L, Vectormath::Aos::mulPerElem(*v1, *v2));
        }
        else
        {
            return luaL_error(L, "%s.%s accepts (%s|%s) as arguments.", SCRIPT_LIB_NAME, "mul_per_elem", SCRIPT_TYPE_NAME_VECTOR3, SCRIPT_TYPE_NAME_VECTOR4);
        }
        return 1;
    }

    static const luaL_reg methods[] =
    {
        {SCRIPT_TYPE_NAME_VECTOR, Vector_new},
        {SCRIPT_TYPE_NAME_VECTOR3, Vector3_new},
        {SCRIPT_TYPE_NAME_VECTOR4, Vector4_new},
        {SCRIPT_TYPE_NAME_QUAT, Quat_new},
        {SCRIPT_TYPE_NAME_MATRIX4, Matrix4_new},
        {"quat_from_to", Quat_FromTo},
        {"quat_axis_angle", Quat_AxisAngle},
        {"quat_basis", Quat_Basis},
        {"quat_rotation_x", Quat_RotationX},
        {"quat_rotation_y", Quat_RotationY},
        {"quat_rotation_z", Quat_RotationZ},
        {"matrix4_frustum", Matrix4_Frustum},
        {"matrix4_look_at", Matrix4_LookAt},
        {"matrix4_orthographic", Matrix4_Orthographic},
        {"matrix4_perspective", Matrix4_Perspective},
        {"matrix4_from_quat", Matrix4_FromQuat},
        {"matrix4_axis_angle", Matrix4_AxisAngle},
        {"matrix4_rotation_x", Matrix4_RotationX},
        {"matrix4_rotation_y", Matrix4_RotationY},
        {"matrix4_rotation_z", Matrix4_RotationZ},
        {"matrix4_translation", Matrix4_Translation},
        {"dot", Dot},
        {"length_sqr", LengthSqr},
        {"length", Length},
        {"normalize", Normalize},
        {"cross", Cross},
        {"lerp", Lerp},
        {"slerp", Slerp},
        {"conj", Conj},
        {"rotate", Rotate},
        {"project", Project},
        {"inv", Inverse},
        {"ortho_inv", OrthoInverse},
        {"mul_per_elem", MulPerElem},
        {0, 0}
    };

    void InitializeVmath(lua_State* L)
    {
        int top = lua_gettop(L);

        const uint32_t type_count = 5;
        struct
        {
            const char* m_Name;
            const luaL_reg* m_Methods;
            const luaL_reg* m_Metatable;
            uint32_t* m_TypeHash;
        } types[type_count] =
        {
            {SCRIPT_TYPE_NAME_VECTOR, Vector_methods, Vector_meta, &TYPE_HASHES[SCRIPT_TYPE_VECTOR]},
            {SCRIPT_TYPE_NAME_VECTOR3, Vector3_methods, Vector3_meta, &TYPE_HASHES[SCRIPT_TYPE_VECTOR3]},
            {SCRIPT_TYPE_NAME_VECTOR4, Vector4_methods, Vector4_meta, &TYPE_HASHES[SCRIPT_TYPE_VECTOR4]},
            {SCRIPT_TYPE_NAME_QUAT, Quat_methods, Quat_meta, &TYPE_HASHES[SCRIPT_TYPE_QUAT]},
            {SCRIPT_TYPE_NAME_MATRIX4, Matrix4_methods, Matrix4_meta, &TYPE_HASHES[SCRIPT_TYPE_MATRIX4]}
        };
        for (uint32_t i = 0; i < type_count; ++i)
        {
            *types[i].m_TypeHash = dmScript::RegisterUserType(L, types[i].m_Name, types[i].m_Methods, types[i].m_Metatable);
        }
        luaL_register(L, SCRIPT_LIB_NAME, methods);
        lua_pop(L, 1);

        assert(top == lua_gettop(L));
    }

    void PushVector(lua_State* L, dmVMath::FloatVector* v)
    {
        dmVMath::FloatVector** vp = (dmVMath::FloatVector**)lua_newuserdata(L, sizeof(dmVMath::FloatVector*));
        *vp = v;
        luaL_getmetatable(L, SCRIPT_TYPE_NAME_VECTOR);
        lua_setmetatable(L, -2);
    }

    dmVMath::FloatVector* CheckVector(lua_State* L, int index)
    {
        return *(dmVMath::FloatVector**)CheckUserType(L, index, TYPE_HASHES[SCRIPT_TYPE_VECTOR], 0);
    }

    void PushVector3(lua_State* L, const Vectormath::Aos::Vector3& v)
    {
        Vectormath::Aos::Vector3* vp = (Vectormath::Aos::Vector3*)lua_newuserdata(L, sizeof(Vectormath::Aos::Vector3));
        *vp = v;
        luaL_getmetatable(L, SCRIPT_TYPE_NAME_VECTOR3);
        lua_setmetatable(L, -2);
    }

    Vectormath::Aos::Vector3* CheckVector3(lua_State* L, int index)
    {
        Vectormath::Aos::Vector3* v = (Vectormath::Aos::Vector3*)CheckUserType(L, index, TYPE_HASHES[SCRIPT_TYPE_VECTOR3], 0);
        if (isnan(v->getX()) || isnan(v->getY()) || isnan(v->getZ()))
        {
            luaL_error(L, "argument #%d contains one or more values which are not numbers: vmath.vector3(%f, %f, %f)", index, v->getX(), v->getY(), v->getZ());
        }
        return v;
    }

    void PushVector4(lua_State* L, const Vectormath::Aos::Vector4& v)
    {
        Vectormath::Aos::Vector4* vp = (Vectormath::Aos::Vector4*)lua_newuserdata(L, sizeof(Vectormath::Aos::Vector4));
        *vp = v;
        luaL_getmetatable(L, SCRIPT_TYPE_NAME_VECTOR4);
        lua_setmetatable(L, -2);
    }

    Vectormath::Aos::Vector4* CheckVector4(lua_State* L, int index)
    {
        Vectormath::Aos::Vector4* v = (Vectormath::Aos::Vector4*)CheckUserType(L, index, TYPE_HASHES[SCRIPT_TYPE_VECTOR4], 0);
        if (isnan(v->getX()) || isnan(v->getY()) || isnan(v->getZ()) || isnan(v->getW()))
        {
            luaL_error(L, "argument #%d contains one or more values which are not numbers: vmath.vector4(%f, %f, %f, %f)", index, v->getX(), v->getY(), v->getZ(), v->getW());
        }
        return v;
    }

    void PushQuat(lua_State* L, const Vectormath::Aos::Quat& q)
    {
        Vectormath::Aos::Quat* qp = (Vectormath::Aos::Quat*)lua_newuserdata(L, sizeof(Vectormath::Aos::Quat));
        *qp = q;
        luaL_getmetatable(L, SCRIPT_TYPE_NAME_QUAT);
        lua_setmetatable(L, -2);
    }

    Vectormath::Aos::Quat* CheckQuat(lua_State* L, int index)
    {
        Vectormath::Aos::Quat* q = (Vectormath::Aos::Quat*)CheckUserType(L, index, TYPE_HASHES[SCRIPT_TYPE_QUAT], 0);
        if (isnan(q->getX()) || isnan(q->getY()) || isnan(q->getZ()) || isnan(q->getW()))
        {
            luaL_error(L, "argument #%d contains one or more values which are not numbers: vmath.quat(%f, %f, %f, %f)", index, q->getX(), q->getY(), q->getZ(), q->getW());
        }
        return q;
    }

    void PushMatrix4(lua_State* L, const Vectormath::Aos::Matrix4& m)
    {
        Vectormath::Aos::Matrix4* mp = (Vectormath::Aos::Matrix4*)lua_newuserdata(L, sizeof(Vectormath::Aos::Matrix4));
        *mp = m;
        luaL_getmetatable(L, SCRIPT_TYPE_NAME_MATRIX4);
        lua_setmetatable(L, -2);
    }

    Vectormath::Aos::Matrix4* CheckMatrix4(lua_State* L, int index)
    {
        Vectormath::Aos::Matrix4* m = (Vectormath::Aos::Matrix4*)CheckUserType(L, index, TYPE_HASHES[SCRIPT_TYPE_MATRIX4], 0);
        if (
            isnan(m->getElem(0, 0)) || isnan(m->getElem(1, 0)) || isnan(m->getElem(2, 0)) || isnan(m->getElem(3, 0)) ||
            isnan(m->getElem(0, 1)) || isnan(m->getElem(1, 1)) || isnan(m->getElem(2, 1)) || isnan(m->getElem(3, 1)) ||
            isnan(m->getElem(0, 2)) || isnan(m->getElem(1, 2)) || isnan(m->getElem(2, 2)) || isnan(m->getElem(3, 2)) ||
            isnan(m->getElem(0, 3)) || isnan(m->getElem(1, 3)) || isnan(m->getElem(2, 3)) || isnan(m->getElem(3, 3))
        )
        {
            luaL_error(L, "argument #%d contains one or more values which are not numbers: vmath.matrix4(%f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f)", index,
                m->getElem(0, 0), m->getElem(1, 0), m->getElem(2, 0), m->getElem(3, 0),
                m->getElem(0, 1), m->getElem(1, 1), m->getElem(2, 1), m->getElem(3, 1),
                m->getElem(0, 2), m->getElem(1, 2), m->getElem(2, 2), m->getElem(3, 2),
                m->getElem(0, 3), m->getElem(1, 3), m->getElem(2, 3), m->getElem(3, 3));
        }
        return m;
    }
}


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_MAXNUMMAG_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_MAXNUMMAG_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

 /*!

    @ingroup group-ieee
    Function object implementing maxnummag capabilities

    Returns the input value which have the greatest absolute value, ignoring nan.

    @par Semantic:

    @code
    auto r = maxnummag(x,y);
    @endcode

    is similar to:

    @code
    auto r = is_nan(x) ? y : (is_nan(y) ? x : maxmag(x, y));
    @endcode

    @see maxmag, is_nan
  **/
  Value maxnummag(Value const & x, Value const& y);
} }
#endif

#include <boost/simd/function/scalar/maxnummag.hpp>
#include <boost/simd/function/simd/maxnummag.hpp>

#endif


#include <boost/utility/string_view.hpp>  // for boost::string_view
#include <ckpttn/FMBiConstrMgr.hpp>       // for FMBiConstrMgr
#include <ckpttn/FMBiGainMgr.hpp>         // for FMBiGainMgr
#include <ckpttn/FMPartMgr.hpp>           // for FMPartMgr
#include <cstdint>                        // for uint8_t
#include <vector>                         // for vector

#include "benchmark/benchmark.h"    // for BENCHMARK, State, BENCHMARK_MAIN
#include "ckpttn/FMBiGainCalc.hpp"  // for FMBiGainCalc
#include "ckpttn/netlist.hpp"       // for SimpleNetlist

extern auto create_test_netlist() -> SimpleNetlist;  // import create_test_netlist
extern auto create_dwarf() -> SimpleNetlist;         // import create_dwarf
extern auto readNetD(boost::string_view netDFileName) -> SimpleNetlist;
extern void readAre(SimpleNetlist& H, boost::string_view areFileName);

/**
 * @brief run FM Bi-partitioning
 *
 * @param[in] H
 * @param[in] option
 */
void run_FMBiPartMgr(const SimpleNetlist& H, bool option) {
    FMBiGainMgr<SimpleNetlist> gainMgr{H};
    gainMgr.gainCalc.special_handle_2pin_nets = option;

    FMBiConstrMgr<SimpleNetlist> constrMgr{H, 0.45};
    FMPartMgr<SimpleNetlist, FMBiGainMgr<SimpleNetlist>, FMBiConstrMgr<SimpleNetlist>> partMgr{
        H, gainMgr, constrMgr};
    std::vector<std::uint8_t> part(H.number_of_modules(), 0);
    partMgr.legalize(part);
    // auto totalcostbefore = partMgr.totalcost;
    partMgr.optimize(part);
    // CHECK(totalcostbefore >= 0);
    // CHECK(partMgr.totalcost <= totalcostbefore);
    // CHECK(partMgr.totalcost >= 0);
}

/**
 * @brief
 *
 * @param[in] state
 */
static void BM_with_2pin_nets(benchmark::State& state) {
    auto H = readNetD("../../testcases/ibm03.net");
    readAre(H, "../../testcases/ibm03.are");

    while (state.KeepRunning()) {
        run_FMBiPartMgr(H, true);
    }
}

// Register the function as a benchmark
BENCHMARK(BM_with_2pin_nets);

//~~~~~~~~~~~~~~~~

/**
 * @brief Define another benchmark
 *
 * @param[in] state
 */
static void BM_without_2pin_nets(benchmark::State& state) {
    auto H = readNetD("../../testcases/ibm03.net");
    readAre(H, "../../testcases/ibm03.are");

    while (state.KeepRunning()) {
        run_FMBiPartMgr(H, false);
    }
}
BENCHMARK(BM_without_2pin_nets);

BENCHMARK_MAIN();

/*
2: ---------------------------------------------------------------
2: Benchmark                     Time             CPU   Iterations
2: ---------------------------------------------------------------
2: BM_with_2pin_nets      86441084 ns     86440541 ns            8
2: BM_without_2pin_nets   95729431 ns     95728056 ns            7
2/4 Test #2: Bench_FMBi_2pin_nets .............   Passed    1.70 sec
*/


#ifndef AIKIDO_PERCEPTION_SHAPE_CONVERSIONS_HPP_
#define AIKIDO_PERCEPTION_SHAPE_CONVERSIONS_HPP_

#include <Eigen/Dense>
#include <geometry_msgs/Pose.h>
#include <tf/LinearMath/Quaternion.h>
#include <tf/LinearMath/Vector3.h>
#include <tf/transform_listener.h>

namespace aikido {
namespace perception {

/// Convert a ROS Pose (position + quaternion) to a 4 x 4 pose matrix
/// \param[in] p the geometry_msgs::Pose pose value
/// return the 4 x 4 homogeneous matrix for the pose
Eigen::Isometry3d convertROSPoseToEigen(const geometry_msgs::Pose& p);

/// Convert a time-stamped ROS transform to a 4 x 4 pose matrix
/// \param[in] t The time-stamped ROS transform (position + quaternion)
/// \return The 4 x 4 homogeneous matrix corresponding to the time-stamped
/// transform
Eigen::Isometry3d convertStampedTransformToEigen(const tf::StampedTransform& t);

} // namespace perception
} // namespace aikido

#endif // AIKIDO_PERCEPTION_SHAPE_CONVERSIONS_HPP_


#pragma once

#include "Concepts.hpp"

#include <boost/hana/tuple.hpp>

namespace codys {

namespace detail {

template <class T, class Tuple>
struct Index;

template <class T, typename... Ts>
struct Index<T, boost::hana::tuple<Ts...>> {
    static constexpr std::size_t index = []() {
        constexpr std::array<bool, sizeof...(Ts)> a{
            {std::is_same<T, Ts>::value...}};

        const auto it = std::find(a.begin(), a.end(), true);

        if (it == a.end()) {
            throw std::runtime_error("Not present");
        }

        return std::distance(a.begin(), it);
    }();
};

} // namespace detail

template <PhysicalType... States>
struct System {
    using UnderlyingType = boost::hana::tuple<States...>;

    template <class WantedStateType> requires SystemStateFor<WantedStateType, System<States...>>
    static constexpr std::size_t idx_of() {
        return detail::Index<WantedStateType, UnderlyingType>::index;
    }
};

} // namespace codys