#include <GL/gl.h>
#include <math.h>
#include <stdio.h>
#include "skyfly.h"

/* static routine decls */

extern int clouds;
static void drawlitmesh_11(float *objdata);
static void drawcolrtexmesh_10(float *objdata);
static void drawclouds(float *objdata);

void drawperfobj(perfobj_t *perfobj)
{
    float     *vdata_ptr =(float *) perfobj->vdata;
    extern void texenv(int), lightpos(void);

    register unsigned long *flagsptr = perfobj->flags;
    register float     *dp;

    while (1) {
        switch (*flagsptr) {
		/*
		 * A paper plane is a single tmesh folded on itself so the orientations
		 * of some triangles in the mesh are incorrect with respect to
		 * their normals. This is solved by drawing the tmesh twice;
		 * first draw only backfaces, then only frontfaces.
		 */
		case PD_DRAW_PAPER_PLANE:
			flagsptr += 1;
			glCullFace(GL_FRONT);
			drawlitmesh_11(vdata_ptr);
			glCullFace(GL_BACK);
			drawlitmesh_11((float *)((perfobj_vert_t *) vdata_ptr + 11));
			glPopMatrix();
			break;

		case PD_DRAW_TERRAIN_CELL:
			dp = *(float **) (flagsptr + 1);
			flagsptr += 2;
			drawcolrtexmesh_10(dp);
			break;

		case PD_DRAW_CLOUDS:
			if (rgbmode)
				glColor3ub(0x30, 0x40, 0xb0);
			glDisable(GL_CULL_FACE);
			glDisable(GL_DEPTH_TEST);
			/*texenv(2);*/
			drawclouds(vdata_ptr);
			glEnable(GL_DEPTH_TEST);
			glEnable(GL_CULL_FACE);
			flagsptr += 1;
			break;

		case PD_PAPER_PLANE_MODE:
			switch (*(flagsptr + 1)) {
			case PLANES_START:
				glEnable(GL_LIGHTING);
				glDisable(GL_TEXTURE_2D);
				if (fog && !rgbmode)
					glDisable(GL_FOG);
				break;
			case PLANES_END:
				glDisable(GL_LIGHTING);
				if (fog && !rgbmode && FOG_LEVELS > 1)
					glEnable(GL_FOG);
				break;
				}	
			flagsptr += 2;
			break;

		case PD_PAPER_PLANE_POS:        /* contains the pushmatrix */
			glPushMatrix();
			glTranslatef(*(vdata_ptr), *(vdata_ptr + 1), *(vdata_ptr + 2));
			glRotatef(*(vdata_ptr + 3), 0.0, 0.0, 1.0);
			glRotatef(*(vdata_ptr + 4), 0.0, 1.0, 0.0);
			glRotatef(*(vdata_ptr + 5), 1.0, 0.0, 0.0);
			flagsptr += 1;
			break;

		case PD_VIEWER_POS:
			glLoadIdentity();
			glRotatef(-90., 1.0, 0., 0.);
			glRotatef(*(vdata_ptr + 3) * RAD_TO_DEG, 0.0, 0.0, 1.0); /* yaw */
			lightpos();
			glTranslatef(-*(vdata_ptr), -*(vdata_ptr + 1), -*(vdata_ptr + 2));
			flagsptr += 1;
			break;

		case PD_TEXTURE_BIND:
			glBindTexture(GL_TEXTURE_2D, *(flagsptr + 1));
			texenv(*(flagsptr + 1));
			glEnable(GL_TEXTURE_2D);
			flagsptr += 2;
			break;

		case PD_END:
			return;

		default:
			fprintf(stderr, "Bad PD primitive %d\n", *flagsptr);
			flagsptr++;
			break;
		}
	}
}

/*
 * Notice how the following routines unwind loops and pre-compute indexes
 * at compile time. This is crucial in obtaining the maximum data transfer 
 * from cpu to the graphics pipe.
 */
static void drawlitmesh_11(float *op)
{
    glBegin(GL_TRIANGLE_STRIP);
    /* one */
    glNormal3fv((op + PD_V_NORMAL));
    glVertex3fv((op + PD_V_POINT));
    /* two */
    glNormal3fv((op + (PD_V_SIZE + PD_V_NORMAL)));
    glVertex3fv((op + (PD_V_SIZE + PD_V_POINT)));
    /* three */
    glNormal3fv((op + (2 * PD_V_SIZE + PD_V_NORMAL)));
    glVertex3fv((op + (2 * PD_V_SIZE + PD_V_POINT)));
    /* four */
    glNormal3fv((op + (3 * PD_V_SIZE + PD_V_NORMAL)));
    glVertex3fv((op + (3 * PD_V_SIZE + PD_V_POINT)));
    /* five */
    glNormal3fv((op + (4 * PD_V_SIZE + PD_V_NORMAL)));
    glVertex3fv((op + (4 * PD_V_SIZE + PD_V_POINT)));
    /* six */
    glNormal3fv((op + (5 * PD_V_SIZE + PD_V_NORMAL)));
    glVertex3fv((op + (5 * PD_V_SIZE + PD_V_POINT)));
    /* seven */
    glNormal3fv((op + (6 * PD_V_SIZE + PD_V_NORMAL)));
    glVertex3fv((op + (6 * PD_V_SIZE + PD_V_POINT)));
    /* eight */
    glNormal3fv((op + (7 * PD_V_SIZE + PD_V_NORMAL)));
    glVertex3fv((op + (7 * PD_V_SIZE + PD_V_POINT)));
    /* nine */
    glNormal3fv((op + (8 * PD_V_SIZE + PD_V_NORMAL)));
    glVertex3fv((op + (8 * PD_V_SIZE + PD_V_POINT)));
    /* ten */
    glNormal3fv((op + (9 * PD_V_SIZE + PD_V_NORMAL)));
    glVertex3fv((op + (9 * PD_V_SIZE + PD_V_POINT)));
    /* eleven */
    glNormal3fv((op + (10 * PD_V_SIZE + PD_V_NORMAL)));
    glVertex3fv((op + (10 * PD_V_SIZE + PD_V_POINT)));

	glEnd();

}

static void drawcolrtexmesh_10(float *op)
{
	glBegin(GL_TRIANGLE_STRIP);
	/* one */
	glTexCoord2fv((op + PD_V_TEX));
    glVertex3fv((op + PD_V_POINT));
    /* two */
    glTexCoord2fv((op + (PD_V_SIZE + PD_V_TEX)));
    glVertex3fv((op + (PD_V_SIZE + PD_V_POINT)));
    /* three */
    glTexCoord2fv((op + (2 * PD_V_SIZE + PD_V_TEX)));
    glVertex3fv((op + (2 * PD_V_SIZE + PD_V_POINT)));
    /* four */
    glTexCoord2fv((op + (3 * PD_V_SIZE + PD_V_TEX)));
    glVertex3fv((op + (3 * PD_V_SIZE + PD_V_POINT)));
    /* five */
    glTexCoord2fv((op + (4 * PD_V_SIZE + PD_V_TEX)));
    glVertex3fv((op + (4 * PD_V_SIZE + PD_V_POINT)));
    /* six */
    glTexCoord2fv((op + (5 * PD_V_SIZE + PD_V_TEX)));
    glVertex3fv((op + (5 * PD_V_SIZE + PD_V_POINT)));
    /* seven */
    glTexCoord2fv((op + (6 * PD_V_SIZE + PD_V_TEX)));
    glVertex3fv((op + (6 * PD_V_SIZE + PD_V_POINT)));
    /* eight */
    glTexCoord2fv((op + (7 * PD_V_SIZE + PD_V_TEX)));
    glVertex3fv((op + (7 * PD_V_SIZE + PD_V_POINT)));
    /* nine */
    glTexCoord2fv((op + (8 * PD_V_SIZE + PD_V_TEX)));
    glVertex3fv((op + (8 * PD_V_SIZE + PD_V_POINT)));
    /* ten */
    glTexCoord2fv((op + (9 * PD_V_SIZE + PD_V_TEX)));
    glVertex3fv((op + (9 * PD_V_SIZE + PD_V_POINT)));

    glEnd();
}

static void drawclouds(float *op)
{
#define SKY_STRIPS 24

	/* Break into quad strips so cheap fog works better */
	if (0 == clouds) {

        GLfloat *vc0, *vc1, *vc2, *vc3;
        GLfloat *tc0, *tc1, *tc2, *tc3;
        GLfloat sky_s0, sky_s1, sky_t0, sky_t1;
        GLfloat sky_x0, sky_x1, sky_y0, sky_y1, sky_z;
        int ii, jj;
        GLfloat s0, s1, t0, t1;
        GLfloat x0, x1, y0, y1, z0;

        vc0 = op + PD_V_POINT;
        vc1 = op + PD_V_POINT + PD_V_SIZE;
        vc2 = op + PD_V_POINT + PD_V_SIZE * 2;
        vc3 = op + PD_V_POINT + PD_V_SIZE * 3;

        tc0 = op + PD_V_TEX;
        tc1 = op + PD_V_TEX + PD_V_SIZE;
        tc2 = op + PD_V_TEX + PD_V_SIZE * 2;
        tc3 = op + PD_V_TEX + PD_V_SIZE * 3;

        sky_s0 = tc0[0];
        sky_s1 = tc1[0];
        sky_t0 = tc0[1];
        sky_t1 = tc2[1];

        sky_x0 = vc0[0];
        sky_x1 = vc1[0];
        sky_y0 = vc0[1];
        sky_y1 = vc2[1];
        sky_z  = vc0[2];

        clouds = glGenLists(1);

        glNewList(clouds, GL_COMPILE);

        s1 = (1.0f / SKY_STRIPS) * (sky_s1 - sky_s0);
        t1 = (1.0f / SKY_STRIPS) * (sky_t1 - sky_t0);
        x1 = (1.0f / SKY_STRIPS) * (sky_x1 - sky_x0);
        y1 = (1.0f / SKY_STRIPS) * (sky_y1 - sky_y0);

        z0 = sky_z;
        s0 = sky_s0;
        x0 = sky_x0;

        for (ii = 0; ii < SKY_STRIPS; ii++, s0 += s1, x0 += x1) {

            t0 = sky_t0;
            y0 = sky_y0;

            glBegin(GL_QUAD_STRIP);

            glTexCoord2f(s0, t0);
            glVertex3f(x0, y0, z0);

            glTexCoord2f(s0 + s1, t0);
            glVertex3f(x0 + x1, y0, z0);

            for (jj = 0; jj < SKY_STRIPS; jj++, t0 += t1, y0 += y1) {

                glTexCoord2f(s0 + s1, t0 + t1);
                glVertex3f(x0 + x1, y0 + y1, z0);

                glTexCoord2f(s0, t0 + t1);
                glVertex3f(x0, y0 + y1, z0);
            }

            glEnd();

        }

        glEndList();
    }

	glCallList(clouds);
}

void putv3fdata(float *v, perfobj_vert_t *ptr)
{
  ptr->vert[0] = v[0];
  ptr->vert[1] = v[1];
  ptr->vert[2] = v[2];
}

void putc3fdata(float *c, perfobj_vert_t *ptr)
{
  ptr->color[0] = c[0];
  ptr->color[1] = c[1];
  ptr->color[2] = c[2];
}

void putn3fdata(float *n, perfobj_vert_t *ptr)
{
  ptr->normal[0] = n[0];
  ptr->normal[1] = n[1];
  ptr->normal[2] = n[2];
}

void putt2fdata(float *t, perfobj_vert_t *ptr)
{
  ptr->texture[0] = t[0];
  ptr->texture[1] = t[1];
}

