/**
 * This file belongs to the 'xlab' game engine.
 * Copyright 2009 xfacter
 * Copyright 2016 wickles
 * This work is licensed under the LGPLv3
 * subject to all terms as reproduced in the included LICENSE file.
 */

/* some prims malloc memory to read calculated values into. we dont want to log every alloc/free, so use stdlib instead of xmem */
#include <stdlib.h>
#include <pspgu.h>
#include <pspgum.h>
#include "xgraphics.h"
#include "xmath.h"

#include "xprim.h"

inline int attr_vtype(int attr)
{
    return (GU_VERTEX_32BITF|(attr & X_PRIM_TEXTURE ? GU_TEXTURE_32BITF : 0)|(attr & X_PRIM_NORMAL ? GU_NORMAL_32BITF : 0));
}

inline int attr_vert_size(int attr)
{
    return (sizeof(float)*(3 + (attr & X_PRIM_TEXTURE ? 2 : 0) + (attr & X_PRIM_NORMAL ? 3 : 0)));
}

#define SET_VECTOR(V, X, Y, Z) V.x = (X); V.y = (Y); V.z = (Z)

#define SET_TEXTURE(ATTR, VERT, IND, U, V) \
    if (ATTR & X_PRIM_TEXTURE)             \
    {                                      \
        VERT[IND++] = U;                   \
        VERT[IND++] = V;                   \
    }

#define SET_NORMAL(ATTR, VERT, IND, X, Y, Z) \
    if (ATTR & X_PRIM_NORMAL)                \
    {                                        \
        VERT[IND++] = X;                     \
        VERT[IND++] = Y;                     \
        VERT[IND++] = Z;                     \
    }

#define SET_VERTEX(VERT, IND, X, Y, Z) \
    VERT[IND++] = X;                         \
    VERT[IND++] = Y;                         \
    VERT[IND++] = Z;

void xPrimTriangle(ScePspFVector3* v0, ScePspFVector3* v1, ScePspFVector3* v2, int attr)
{
    xGuSaveStates();
    if (attr & X_PRIM_NO_CULL) sceGuDisable(GU_CULL_FACE);
    float* vertices = (float*)sceGuGetMemory(3*attr_vert_size(attr));
    int index_f = 0;
    ScePspFVector3 normal;
    if (attr & X_PRIM_NORMAL) x_normal(&normal, v0, v1, v2);
    SET_TEXTURE(attr, vertices, index_f, 0.0f, 0.0f);
    SET_NORMAL(attr, vertices, index_f, normal.x, normal.y, normal.z);
    SET_VERTEX(vertices, index_f, v0->x, v0->y, v0->z);
    SET_TEXTURE(attr, vertices, index_f, 0.5f, 1.0f);
    SET_NORMAL(attr, vertices, index_f, normal.x, normal.y, normal.z);
    SET_VERTEX(vertices, index_f, v1->x, v1->y, v1->z);
    SET_TEXTURE(attr, vertices, index_f, 1.0f, 0.0f);
    SET_NORMAL(attr, vertices, index_f, normal.x, normal.y, normal.z);
    SET_VERTEX(vertices, index_f, v2->x, v2->y, v2->z);
    sceGumDrawArray(GU_TRIANGLES, attr_vtype(attr)|GU_TRANSFORM_3D, 3, 0, vertices);
    xGuLoadStates();
}

void xPrimPlane(float width, float height, int attr)
{
    xGuSaveStates();
    if (attr & X_PRIM_NO_CULL) sceGuDisable(GU_CULL_FACE);
    float* vertices = (float*)sceGuGetMemory(4*attr_vert_size(attr));
    int index_f = 0;
    SET_TEXTURE(attr, vertices, index_f, 0.0f, 0.0f);
    SET_NORMAL(attr, vertices, index_f, 0.0f, 0.0f, 1.0f);
    SET_VERTEX(vertices, index_f, -0.5f*width, -0.5f*height, 0.0f);
    SET_TEXTURE(attr, vertices, index_f, 0.0f, 1.0f);
    SET_NORMAL(attr, vertices, index_f, 0.0f, 0.0f, 1.0f);
    SET_VERTEX(vertices, index_f, -0.5f*width, 0.5f*height, 0.0f);
    SET_TEXTURE(attr, vertices, index_f, 1.0f, 1.0f);
    SET_NORMAL(attr, vertices, index_f, 0.0f, 0.0f, 1.0f);
    SET_VERTEX(vertices, index_f, 0.5f*width, 0.5f*height, 0.0f);
    SET_TEXTURE(attr, vertices, index_f, 1.0f, 0.0f);
    SET_NORMAL(attr, vertices, index_f, 0.0f, 0.0f, 1.0f);
    SET_VERTEX(vertices, index_f, 0.5f*width, -0.5f*height, 0.0f);
    sceGumDrawArray(GU_TRIANGLE_FAN, attr_vtype(attr)|GU_TRANSFORM_3D, 4, 0, vertices);
    xGuLoadStates();
}

void xPrimBox(float x_length, float y_length, float z_length, int attr)
{
    xGuSaveStates();
    if (attr & X_PRIM_NO_CULL) sceGuDisable(GU_CULL_FACE);
    float* vertices = (float*)sceGuGetMemory(4*6*attr_vert_size(attr));
    int index_f = 0;
    int i;
    for (i = 0; i < 2; i++)
    {
        SET_TEXTURE(attr, vertices, index_f, (i == 0 ? 0.0f : 1.0f), 0.0f);
        SET_NORMAL(attr, vertices, index_f, (i == 0 ? -1.0f : 1.0f), 0.0f, 0.0f);
        SET_VERTEX(vertices, index_f, (i == 0 ? -0.5f : 0.5f)*x_length, -0.5f*y_length, -0.5f*z_length);
        SET_TEXTURE(attr, vertices, index_f, (i == 0 ? 0.0f : 1.0f), 1.0f);
        SET_NORMAL(attr, vertices, index_f, (i == 0 ? -1.0f : 1.0f), 0.0f, 0.0f);
        SET_VERTEX(vertices, index_f, (i == 0 ? -0.5f : 0.5f)*x_length, 0.5f*y_length, -0.5f*z_length);
        SET_TEXTURE(attr, vertices, index_f, (i == 0 ? 1.0f : 0.0f), 1.0f);
        SET_NORMAL(attr, vertices, index_f, (i == 0 ? -1.0f : 1.0f), 0.0f, 0.0f);
        SET_VERTEX(vertices, index_f, (i == 0 ? -0.5f : 0.5f)*x_length, 0.5f*y_length, 0.5f*z_length);
        SET_TEXTURE(attr, vertices, index_f, (i == 0 ? 1.0f : 0.0f), 0.0f);
        SET_NORMAL(attr, vertices, index_f, (i == 0 ? -1.0f : 1.0f), 0.0f, 0.0f);
        SET_VERTEX(vertices, index_f, (i == 0 ? -0.5f : 0.5f)*x_length, -0.5f*y_length, 0.5f*z_length);
        if (i == 0) sceGuFrontFace(GU_CW);
        else sceGuFrontFace(GU_CCW);
        sceGumDrawArray(GU_TRIANGLE_FAN, attr_vtype(attr)|GU_TRANSFORM_3D, 4, 0, (void*)((u32)vertices + (0 + i)*4*attr_vert_size(attr)));
    }
    for (i = 0; i < 2; i++)
    {
        SET_TEXTURE(attr, vertices, index_f, (i == 0 ? 0.0f : 1.0f), 0.0f);
        SET_NORMAL(attr, vertices, index_f, 0.0f, (i == 0 ? -1.0f : 1.0f), 0.0f);
        SET_VERTEX(vertices, index_f, 0.5f*x_length, (i == 0 ? -0.5f : 0.5f)*y_length, -0.5f*z_length);
        SET_TEXTURE(attr, vertices, index_f, (i == 0 ? 0.0f : 1.0f), 1.0f);
        SET_NORMAL(attr, vertices, index_f, 0.0f, (i == 0 ? -1.0f : 1.0f), 0.0f);
        SET_VERTEX(vertices, index_f, -0.5f*x_length, (i == 0 ? -0.5f : 0.5f)*y_length, -0.5f*z_length);
        SET_TEXTURE(attr, vertices, index_f, (i == 0 ? 1.0f : 0.0f), 1.0f);
        SET_NORMAL(attr, vertices, index_f, 0.0f, (i == 0 ? -1.0f : 1.0f), 0.0f);
        SET_VERTEX(vertices, index_f, -0.5f*x_length, (i == 0 ? -0.5f : 0.5f)*y_length, 0.5f*z_length);
        SET_TEXTURE(attr, vertices, index_f, (i == 0 ? 1.0f : 0.0f), 0.0f);
        SET_NORMAL(attr, vertices, index_f, 0.0f, (i == 0 ? -1.0f : 1.0f), 0.0f);
        SET_VERTEX(vertices, index_f, 0.5f*x_length, (i == 0 ? -0.5f : 0.5f)*y_length, 0.5f*z_length);
        if (i == 0) sceGuFrontFace(GU_CW);
        else sceGuFrontFace(GU_CCW);
        sceGumDrawArray(GU_TRIANGLE_FAN, attr_vtype(attr)|GU_TRANSFORM_3D, 4, 0, (void*)((u32)vertices + (2 + i)*4*attr_vert_size(attr)));
    }
    for (i = 0; i < 2; i++)
    {
        SET_TEXTURE(attr, vertices, index_f, (i == 0 ? 0.0f : 1.0f), 0.0f);
        SET_NORMAL(attr, vertices, index_f, 0.0f, 0.0f, (i == 0 ? -1.0f : 1.0f));
        SET_VERTEX(vertices, index_f, -0.5f*x_length, 0.5f*y_length, (i == 0 ? -0.5f : 0.5f)*z_length);
        SET_TEXTURE(attr, vertices, index_f, (i == 0 ? 0.0f : 1.0f), 1.0f);
        SET_NORMAL(attr, vertices, index_f, 0.0f, 0.0f, (i == 0 ? -1.0f : 1.0f));
        SET_VERTEX(vertices, index_f, -0.5f*x_length, -0.5f*y_length, (i == 0 ? -0.5f : 0.5f)*z_length);
        SET_TEXTURE(attr, vertices, index_f, (i == 0 ? 1.0f : 0.0f), 1.0f);
        SET_NORMAL(attr, vertices, index_f, 0.0f, 0.0f, (i == 0 ? -1.0f : 1.0f));
        SET_VERTEX(vertices, index_f, 0.5f*x_length, -0.5f*y_length, (i == 0 ? -0.5f : 0.5f)*z_length);
        SET_TEXTURE(attr, vertices, index_f, (i == 0 ? 1.0f : 0.0f), 0.0f);
        SET_NORMAL(attr, vertices, index_f, 0.0f, 0.0f, (i == 0 ? -1.0f : 1.0f));
        SET_VERTEX(vertices, index_f, 0.5f*x_length, 0.5f*y_length, (i == 0 ? -0.5f : 0.5f)*z_length);
        if (i == 0) sceGuFrontFace(GU_CW);
        else sceGuFrontFace(GU_CCW);
        sceGumDrawArray(GU_TRIANGLE_FAN, attr_vtype(attr)|GU_TRANSFORM_3D, 4, 0, (void*)((u32)vertices + (4 + i)*4*attr_vert_size(attr)));
    }
    sceGuFrontFace(GU_CW);
    xGuLoadStates();
}

inline void xPrimCube(float length, int attr)
{
    xPrimBox(length, length, length, attr);
}

static void drawUnitSphere(int slices, int rows, int attr)
{
    xGuSaveStates();
    if (attr & X_PRIM_NO_CULL) sceGuDisable(GU_CULL_FACE);
    slices += 1;
    float* vertices = (float*)sceGuGetMemory((slices*rows)*attr_vert_size(attr));
    u16* indices = (u16*)sceGuGetMemory((6*(slices-1)*(rows-1))*sizeof(u16));

    float* slice_cos = (float*)malloc(slices*sizeof(float));
    float* slice_sin = (float*)malloc(slices*sizeof(float));
    float* row_cos = (float*)malloc(rows*sizeof(float));
    float* row_sin = (float*)malloc(rows*sizeof(float));

    float slice_inv = 1.0f/(slices-1);
    float row_inv = 1.0f/(rows-1);

    int i, j, i1, j1;

    for (i = 0; i < slices; i++)
    {
        x_sincos(i*2*X_PI*slice_inv, &slice_sin[i], &slice_cos[i]);
    }
    for (i = 0; i < rows; i++)
    {
        x_sincos(i*X_PI*row_inv, &row_sin[i], &row_cos[i]);
    }

    int index = 0;

    for (i = 0; i < slices; i++)
    {
        for (j = 0; j < rows; j++)
        {
            SET_TEXTURE(attr, vertices, index, i*slice_inv, j*row_inv);
            SET_NORMAL(attr, vertices, index, slice_cos[i]*row_sin[j], slice_sin[i]*row_sin[j], row_cos[j]);
            SET_VERTEX(vertices, index, slice_cos[i]*row_sin[j], slice_sin[i]*row_sin[j], row_cos[j]);
        }
    }

    free(slice_cos);
    free(slice_sin);
    free(row_cos);
    free(row_sin);

    index = 0;

    for (i = 0; i < slices-1; i++)
    {
        for (j = 0; j < rows-1; j++)
        {
            i1 = i+1;
            j1 = j+1;

    		indices[index++] = i * rows + j;
    		indices[index++] = i1 * rows + j1;
    		indices[index++] = i * rows + j1;

    		indices[index++] = i * rows + j;
    		indices[index++] = i1 * rows + j;
    		indices[index++] = i1 * rows + j1;
        }
    }

    sceGumDrawArray(GU_TRIANGLES, attr_vtype(attr)|GU_INDEX_16BIT|GU_TRANSFORM_3D, 6*(slices-1)*(rows-1), indices, vertices);
    xGuLoadStates();
}

inline void xPrimEllipsoid(float x_radius, float y_radius, float z_radius, int slices, int rows, int attr)
{
    sceGumPushMatrix();
    xGumScale(x_radius, y_radius, z_radius);
    drawUnitSphere(slices, rows, attr);
    sceGumPopMatrix();
}

inline void xPrimSphere(float radius, int slices, int rows, int attr)
{
    xPrimEllipsoid(radius, radius, radius, slices, rows, attr);
}

void xPrimPyramid(float length, float width, float height, int attr)
{
    xGuSaveStates();
    if (attr & X_PRIM_NO_CULL) sceGuDisable(GU_CULL_FACE);
    float* vertices = (float*)sceGuGetMemory((4*3 + 2*3)*attr_vert_size(attr));
    int index_f = 0;
    ScePspFVector3 normal, p1, p2, p3;

    SET_TEXTURE(attr, vertices, index_f, 0.0f, 0.0f);
    SET_NORMAL(attr, vertices, index_f, 0.0f, 0.0f, -1.0f);
    SET_VERTEX(vertices, index_f, -0.5f*length, 0.5f*width, -0.5f*height);
    SET_TEXTURE(attr, vertices, index_f, 0.0f, 1.0f);
    SET_NORMAL(attr, vertices, index_f, 0.0f, 0.0f, -1.0f);
    SET_VERTEX(vertices, index_f, -0.5f*length, -0.5f*width, -0.5f*height);
    SET_TEXTURE(attr, vertices, index_f, 1.0f, 1.0f);
    SET_NORMAL(attr, vertices, index_f, 0.0f, 0.0f, -1.0f);
    SET_VERTEX(vertices, index_f, 0.5f*length, -0.5f*width, -0.5f*height);

    SET_TEXTURE(attr, vertices, index_f, 1.0f, 1.0f);
    SET_NORMAL(attr, vertices, index_f, 0.0f, 0.0f, -1.0f);
    SET_VERTEX(vertices, index_f, 0.5f*length, -0.5f*width, -0.5f*height);
    SET_TEXTURE(attr, vertices, index_f, 1.0f, 0.0f);
    SET_NORMAL(attr, vertices, index_f, 0.0f, 0.0f, -1.0f);
    SET_VERTEX(vertices, index_f, 0.5f*length, 0.5f*width, -0.5f*height);
    SET_TEXTURE(attr, vertices, index_f, 0.0f, 0.0f);
    SET_NORMAL(attr, vertices, index_f, 0.0f, 0.0f, -1.0f);
    SET_VERTEX(vertices, index_f, -0.5f*length, 0.5f*width, -0.5f*height);

    int i;
    for (i = 0; i < 4; i++)
    {
        if (i == 0)
        {
            SET_VECTOR(p1,  0.5f*length, -0.5f*width, -0.5f*height);
            SET_VECTOR(p2, -0.5f*length, -0.5f*width, -0.5f*height);
        }
        else if (i == 1)
        {
            SET_VECTOR(p1, -0.5f*length, -0.5f*width, -0.5f*height);
            SET_VECTOR(p2, -0.5f*length,  0.5f*width, -0.5f*height);
        }
        else if (i == 2)
        {
            SET_VECTOR(p1, -0.5f*length,  0.5f*width, -0.5f*height);
            SET_VECTOR(p2,  0.5f*length,  0.5f*width, -0.5f*height);
        }
        else //if (i == 3)
        {
            SET_VECTOR(p1,  0.5f*length,  0.5f*width, -0.5f*height);
            SET_VECTOR(p2,  0.5f*length, -0.5f*width, -0.5f*height);
        }
        SET_VECTOR(p3, 0.0f, 0.0f, 0.5f*height);
        if (attr & X_PRIM_NORMAL) x_normal(&normal, &p1, &p2, &p3);
        SET_TEXTURE(attr, vertices, index_f, 0.0f, 0.0f);
        SET_NORMAL(attr, vertices, index_f, normal.x, normal.y, normal.z);
        SET_VERTEX(vertices, index_f, p1.x, p1.y, p1.z);
        SET_TEXTURE(attr, vertices, index_f, 0.5f, 1.0f);
        SET_NORMAL(attr, vertices, index_f, normal.x, normal.y, normal.z);
        SET_VERTEX(vertices, index_f, p2.x, p2.y, p2.z);
        SET_TEXTURE(attr, vertices, index_f, 1.0f, 0.0f);
        SET_NORMAL(attr, vertices, index_f, normal.x, normal.y, normal.z);
        SET_VERTEX(vertices, index_f, p3.x, p3.y, p3.z);
    }

    sceGumDrawArray(GU_TRIANGLES, attr_vtype(attr)|GU_TRANSFORM_3D, 18, 0, vertices);
    xGuLoadStates();
}

void xPrimCone(float radius, float height, int slices, int attr)
{
    xGuSaveStates();
    if (attr & X_PRIM_NO_CULL) sceGuDisable(GU_CULL_FACE);
    slices += 1;
    float* vertices = (float*)sceGuGetMemory((2*3*(slices-1))*attr_vert_size(attr));

    float* slice_cos = (float*)malloc(slices*sizeof(float));
    float* slice_sin = (float*)malloc(slices*sizeof(float));

    float slice_inv = 1.0f/(slices-1);

    int i, i1, index;

    for (i = 0; i < slices; i++)
    {
        x_sincos(i*2*X_PI*slice_inv, &slice_sin[i], &slice_cos[i]);
    }

    index = 0;
    for (i = 0; i < slices-1; i++)
    {
        i1 = i+1;

        SET_TEXTURE(attr, vertices, index, i*slice_inv, 0.0f);
        SET_NORMAL(attr, vertices, index, 0.0f, 0.0f, -1.0f);
        SET_VERTEX(vertices, index, radius*slice_cos[i], radius*slice_sin[i], -0.5f*height);
        SET_TEXTURE(attr, vertices, index, i1*slice_inv, 0.0f);
        SET_NORMAL(attr, vertices, index, 0.0f, 0.0f, -1.0f);
        SET_VERTEX(vertices, index, radius*slice_cos[i1], radius*slice_sin[i1], -0.5f*height);
        SET_TEXTURE(attr, vertices, index, (i+0.5f)*slice_inv, 1.0f);
        SET_NORMAL(attr, vertices, index, 0.0f, 0.0f, -1.0f);
        SET_VERTEX(vertices, index, 0.0f, 0.0f, -0.5f*height);
    }

    ScePspFVector3 normal, p1, p2, p3;

    for (i = 0; i < slices-1; i++)
    {
        i1 = i+1;

        SET_VECTOR(p1, radius*slice_cos[i1], radius*slice_sin[i1], -0.5f*height);
        SET_VECTOR(p2, radius*slice_cos[i], radius*slice_sin[i], -0.5f*height);
        SET_VECTOR(p3, 0.0f, 0.0f, 0.5f*height);

        if (attr & X_PRIM_NORMAL) x_normal(&normal, &p1, &p2, &p3);

        SET_TEXTURE(attr, vertices, index, i1*slice_inv, 0.0f);
        SET_NORMAL(attr, vertices, index, normal.x, normal.y, normal.z);
        SET_VERTEX(vertices, index, p1.x, p1.y, p1.z);
        SET_TEXTURE(attr, vertices, index, i*slice_inv, 0.0f);
        SET_NORMAL(attr, vertices, index, normal.x, normal.y, normal.z);
        SET_VERTEX(vertices, index, p2.x, p2.y, p2.z);
        SET_TEXTURE(attr, vertices, index, (i+0.5f)*slice_inv, 1.0f);
        SET_NORMAL(attr, vertices, index, normal.x, normal.y, normal.z);
        SET_VERTEX(vertices, index, p3.x, p3.y, p3.z);
    }

    free(slice_cos);
    free(slice_sin);

    sceGumDrawArray(GU_TRIANGLES, attr_vtype(attr)|GU_TRANSFORM_3D, 2*3*(slices-1), 0, vertices);
    xGuLoadStates();
}

void xPrimCylinder(float radius, float height, int slices, int attr)
{
    xGuSaveStates();
    if (attr & X_PRIM_NO_CULL) sceGuDisable(GU_CULL_FACE);
    slices += 1;
    float* vertices = (float*)sceGuGetMemory(((6+2*3)*(slices-1))*attr_vert_size(attr));

    float* slice_cos = (float*)malloc(slices*sizeof(float));
    float* slice_sin = (float*)malloc(slices*sizeof(float));

    float slice_inv = 1.0f/(slices-1);

    int i, i1, index;

    for (i = 0; i < slices; i++)
    {
        x_sincos(i*2*X_PI*slice_inv, &slice_sin[i], &slice_cos[i]);
    }

    index = 0;
    for (i = 0; i < slices-1; i++)
    {
        i1 = i+1;

        SET_TEXTURE(attr, vertices, index, i*slice_inv, 0.0f);
        SET_NORMAL(attr, vertices, index, 0.0f, 0.0f, -1.0f);
        SET_VERTEX(vertices, index, radius*slice_cos[i], radius*slice_sin[i], -0.5f*height);
        SET_TEXTURE(attr, vertices, index, i1*slice_inv, 0.0f);
        SET_NORMAL(attr, vertices, index, 0.0f, 0.0f, -1.0f);
        SET_VERTEX(vertices, index, radius*slice_cos[i1], radius*slice_sin[i1], -0.5f*height);
        SET_TEXTURE(attr, vertices, index, (i+0.5f)*slice_inv, 1.0f);
        SET_NORMAL(attr, vertices, index, 0.0f, 0.0f, -1.0f);
        SET_VERTEX(vertices, index, 0.0f, 0.0f, -0.5f*height);
    }

    for (i = 0; i < slices-1; i++)
    {
        i1 = i+1;

        SET_TEXTURE(attr, vertices, index, i1*slice_inv, 0.0f);
        SET_NORMAL(attr, vertices, index, 0.0f, 0.0f, 1.0f);
        SET_VERTEX(vertices, index, radius*slice_cos[i1], radius*slice_sin[i1], 0.5f*height);
        SET_TEXTURE(attr, vertices, index, i*slice_inv, 0.0f);
        SET_NORMAL(attr, vertices, index, 0.0f, 0.0f, 1.0f);
        SET_VERTEX(vertices, index, radius*slice_cos[i], radius*slice_sin[i], 0.5f*height);
        SET_TEXTURE(attr, vertices, index, (i+0.5f)*slice_inv, 1.0f);
        SET_NORMAL(attr, vertices, index, 0.0f, 0.0f, 1.0f);
        SET_VERTEX(vertices, index, 0.0f, 0.0f, 0.5f*height);
    }

    for (i = 0; i < slices-1; i++)
    {
        i1 = i+1;

        SET_TEXTURE(attr, vertices, index, i1*slice_inv, 0.0f);
        SET_NORMAL(attr, vertices, index, slice_cos[i1], slice_sin[i1], 0.0f);
        SET_VERTEX(vertices, index, radius*slice_cos[i1], radius*slice_sin[i1], -0.5f*height);
        SET_TEXTURE(attr, vertices, index, i*slice_inv, 0.0f);
        SET_NORMAL(attr, vertices, index, slice_cos[i], slice_sin[i], 0.0f);
        SET_VERTEX(vertices, index, radius*slice_cos[i], radius*slice_sin[i], -0.5f*height);
        SET_TEXTURE(attr, vertices, index, i*slice_inv, 1.0f);
        SET_NORMAL(attr, vertices, index, slice_cos[i], slice_sin[i], 0.0f);
        SET_VERTEX(vertices, index, radius*slice_cos[i], radius*slice_sin[i], 0.5f*height);

        SET_TEXTURE(attr, vertices, index, i*slice_inv, 1.0f);
        SET_NORMAL(attr, vertices, index, slice_cos[i], slice_sin[i], 0.0f);
        SET_VERTEX(vertices, index, radius*slice_cos[i], radius*slice_sin[i], 0.5f*height);
        SET_TEXTURE(attr, vertices, index, i1*slice_inv, 1.0f);
        SET_NORMAL(attr, vertices, index, slice_cos[i1], slice_sin[i1], 0.0f);
        SET_VERTEX(vertices, index, radius*slice_cos[i1], radius*slice_sin[i1], 0.5f*height);
        SET_TEXTURE(attr, vertices, index, i1*slice_inv, 0.0f);
        SET_NORMAL(attr, vertices, index, slice_cos[i1], slice_sin[i1], 0.0f);
        SET_VERTEX(vertices, index, radius*slice_cos[i1], radius*slice_sin[i1], -0.5f*height);
    }

    free(slice_cos);
    free(slice_sin);

    sceGumDrawArray(GU_TRIANGLES, attr_vtype(attr)|GU_TRANSFORM_3D, (6+2*3)*(slices-1), 0, vertices);
    xGuLoadStates();
}

void xPrimTorus(float radius, float thickness, int slices, int rows, int attr)
{
    xGuSaveStates();
    if (attr & X_PRIM_NO_CULL) sceGuDisable(GU_CULL_FACE);
    slices += 1;
    rows += 1;
    float* vertices = (float*)sceGuGetMemory(slices*rows*attr_vert_size(attr));
    u16* indices = (u16*)sceGuGetMemory(6*(slices-1)*(rows-1)*sizeof(u16));

    float* slice_cos = (float*)malloc(slices*sizeof(float));
    float* slice_sin = (float*)malloc(slices*sizeof(float));
    float* row_cos = (float*)malloc(rows*sizeof(float));
    float* row_sin = (float*)malloc(rows*sizeof(float));

    float slice_inv = 1.0f/(slices-1);
    float row_inv = 1.0f/(rows-1);

    int i, j, i1, j1, index;

    for (i = 0; i < slices; i++)
    {
        x_sincos(i*2*X_PI*slice_inv, &slice_sin[i], &slice_cos[i]);
    }
    for (i = 0; i < rows; i++)
    {
        x_sincos(i*2*X_PI*row_inv, &row_sin[i], &row_cos[i]);
    }

    index = 0;
    for (i = 0; i < slices; i++)
    {
    	for (j = 0; j < rows; j++)
    	{
            SET_TEXTURE(attr, vertices, index, i*slice_inv, j*row_inv); //slice_cos[i]*row_cos[j], slice_cos[i]*row_sin[j]);
            SET_NORMAL(attr, vertices, index, slice_cos[i]*row_cos[j], slice_sin[i]*row_cos[j], row_sin[j]);
            SET_VERTEX(vertices, index, (radius + thickness*row_cos[j]) * slice_cos[i], (radius + thickness*row_cos[j]) * slice_sin[i], thickness*row_sin[j]);
    	}
    }

    free(slice_cos);
    free(slice_sin);
    free(row_cos);
    free(row_sin);

    index = 0;
    for (i = 0; i < slices-1; i++)
    {
        for (j = 0; j < rows-1; j++)
        {
            i1 = i+1;
            j1 = j+1;

    		indices[index++] = i * rows + j;
    		indices[index++] = i * rows + j1;
    		indices[index++] = i1 * rows + j1;

    		indices[index++] = i1 * rows + j1;
    		indices[index++] = i1 * rows + j;
    		indices[index++] = i * rows + j;
        }
    }

    sceGumDrawArray(GU_TRIANGLES, attr_vtype(attr)|GU_INDEX_16BIT|GU_TRANSFORM_3D, 6*(slices-1)*(rows-1), indices, vertices);
    xGuLoadStates();
}