#include <stdio.h>
#include <math.h>
#include <malloc.h>
#include "skyfly.h"

#define cosf(a)		cos((float)a)
#define sinf(a)  	sin((float)a)
#define sqrtf(a)  	sqrt((float)a)
#define expf(a)  	exp((float)a)

static void create_terrain(void);
static void erode_terrain(void);
static void color_terrain(void);
static void init_cells(void);
static void put_cell(float *source, perfobj_t *pobj);
static void put_paper_plane(float *source, perfobj_t *pobj);
static void put_texture_bind(int bind, perfobj_t *pobj);

int clouds;
static float paper_plane_vertexes[] = {
/*Nx  Ny  Nz   Vx     Vy    Vz */
/* ----------------------------    Top view of plane, middle streached open  */
 0.2, 0., .98, -.10,    0,  .02,/* vertex #'s      4 (.48,0,-.06)            */
 0., 0., 1.,   -.36,  .20, -.04,/*                 .                         */
 0., 0., 1.,    .36,  .01,    0,/*                ...                        */
 0., 0.,-1.,   -.32,  .02,    0,/*                 .             +X          */
 0., 1., 0.,    .48,    0, -.06,/*               2 . 6,8          ^          */
 0., 1., 0.,   -.30,    0, -.12,/*               . . .            |          */
 0.,-1., 0.,    .36, -.01,    0,/*              .. . ..           |          */
 0.,-1., 0.,   -.32, -.02,    0,/*               . . .            |          */
 0., 0.,-1.,    .36, -.01,    0,/*             . . . . .  +Y<-----*          */
 0., 0.,-1.,   -.36, -.20, -.04,/*               . . .     for this picture  */
 -0.2, 0., .98,  -.10,  0,  .02,/*            .  . . .  .  coord system rot. */
 -0.2, 0., -.98, -.10,  0,  .02,/*               . . .     90 degrees        */
 0., 0., -1.,  -.36,  .20, -.04,/*           .   . . .   .                   */
 0., 0., -1.,   .36,  .01,    0,/*               . # .           # marks     */
 0., 0., 1.,   -.32,  .02,    0,/*          .    . . .    .   (0,0) origin   */
 0., -1., 0.,   .48,    0, -.06,/*               . . .         (z=0 at top   */
 0., -1., 0.,  -.30,    0, -.12,/*         .     0 . 10    .    of plane)    */
 0.,1., 0.,     .36, -.01,    0,/*             . . . . .                     */
 0.,1., 0.,    -.32, -.02,    0,/*        .  .   . . .   .  .                */
 0., 0.,1.,     .36, -.01,    0,/*         .     . . .     .                 */
 0., 0.,1.,    -.36, -.20, -.04,/*       1.......3.5.7.......9               */
 0.2, 0., -.98,  -.10,  0,  .02,/* (-.36,.2,-.04)                            */
};

#define SIZE    400

float *A;

void init_paper_planes(void)
{
    perfobj_t       *pobj;

    /* 
     * create various perf-objs for planes 
     */
    pobj = &(SharedData->paper_plane_obj);
    pobj->flags = SharedData->paper_plane_flags;
    pobj->vdata = (float *) SharedData->paper_plane_verts;
    put_paper_plane(paper_plane_vertexes, pobj);

    pobj = &(SharedData->paper_plane_start_obj);
    pobj->flags = SharedData->paper_plane_start_flags;
    *(pobj->flags) = PD_PAPER_PLANE_MODE;
    *(pobj->flags + 1) = PLANES_START;
    *(pobj->flags + 2) = PD_END;

    pobj = &(SharedData->paper_plane_2ndpass_obj);
    pobj->flags = SharedData->paper_plane_2ndpass_flags;
    *(pobj->flags) = PD_PAPER_PLANE_MODE;
    *(pobj->flags + 1) = PLANES_SECOND_PASS;
    *(pobj->flags + 2) = PD_END;

    pobj = &(SharedData->paper_plane_end_obj);
    pobj->flags = SharedData->paper_plane_end_flags;
    *(pobj->flags) = PD_PAPER_PLANE_MODE;
    *(pobj->flags + 1) = PLANES_END;
    *(pobj->flags + 2) = PD_END;
}


/*
 * create perfobj from static definition of plane geometry above
 */

static void put_paper_plane(float *source, perfobj_t *pobj)
{
    int             j;
    perfobj_vert_t *pdataptr =(perfobj_vert_t *) pobj->vdata;
    unsigned long  *flagsptr = pobj->flags;
    float          *sp = source;

    *flagsptr++ = PD_DRAW_PAPER_PLANE;

    for (j = 0; j < 22; j++) {
        putn3fdata(sp + 0, pdataptr);
        putv3fdata(sp + 3, pdataptr);

        sp += 6;
        pdataptr++;
    }
    *flagsptr++ = PD_END;

}

static void put_texture_bind(int bind, perfobj_t *pobj)
{
	unsigned long  *flagsptr = pobj->flags;

	*flagsptr++ = PD_TEXTURE_BIND;
	*flagsptr++ = bind;

	*flagsptr++ = PD_END;

}

static void put_clouds_vert(float s, float t, float x, float y, float z,
				perfobj_vert_t *pdataptr)
{
	float           D[5];
	D[0] = s;
	D[1] = t;
	D[2] = x;
	D[3] = y;
	D[4] = z;
	putt2fdata(D, pdataptr);
	putv3fdata(D + 2, pdataptr);
}

float S[SIZE][SIZE];
float T[SIZE][SIZE];
float C[SIZE][SIZE][3];
int   M[SIZE][SIZE];

void init_terrain(void)
{
	GridDim = CellDim * NumCells;
	XYScale = (float)GRID_RANGE / (float)GridDim;
	CellSize = (float)GRID_RANGE / (float)NumCells;

	create_terrain();
	erode_terrain();
	color_terrain();
	init_cells();
}

#define SKY                             50.

void init_clouds(void)
{
    perfobj_t *pobj;
    perfobj_vert_t *pdataptr;

	clouds = 0;
    pobj = &(SharedData->clouds_texture_obj);
    pobj->flags = SharedData->clouds_texture_flags;
    put_texture_bind(2,pobj);

    pobj = &(SharedData->clouds_obj);
    pobj->flags = SharedData->clouds_flags;
    pobj->vdata = (float *)SharedData->clouds_verts;
    *(pobj->flags+ 0) = PD_DRAW_CLOUDS;
    *(pobj->flags+ 1) = PD_END;

    pdataptr =(perfobj_vert_t *) pobj->vdata;

    put_clouds_vert(0.,0., -SKY, -SKY, SKY_HIGH,pdataptr);
    pdataptr++;
    put_clouds_vert(24.,0.,  SKY+GRID_RANGE, -SKY, SKY_HIGH,pdataptr);
    pdataptr++;
    put_clouds_vert(24.,24.,  SKY+GRID_RANGE,  SKY+GRID_RANGE, SKY_HIGH,pdataptr);
    pdataptr++;
    put_clouds_vert(0.,24., -SKY,  SKY+GRID_RANGE, SKY_HIGH,pdataptr);
}

static void create_terrain(void)
{
	int             r, c, i, x1, y1, x2, y2;
	int             hillsize;

    hillsize = GRID_RANGE / 12;

    A = (float*)calloc(GridDim * GridDim, sizeof(float));

    /*
     *  initialize elevation to zero, except band down middle
     *  where make a maximum height 'hill' that will later be
     *  inverted to make the negative elevation 'canyon'
     */

    for (r = 0; r < GridDim; r++)
        for (c = 0; c < GridDim; c++)
             if(r>=(GridDim/2-2-IRND(2)) && r<=(GridDim/2+2+IRND(2)))
                A[r * GridDim + c] = 1.0;
             else
                A[r * GridDim + c] = 0.0;

    /*
     * create random sinusoidal hills that add on top
     * of each other
     */
    for (i = 1; i <= 10*GridDim; i++) {

        /* randomly position hill */
        x1 = IRND(GridDim - hillsize);
        x2 = x1 + hillsize/ 8 + IRND(hillsize-hillsize/ 8);
        y1 = IRND(GridDim - hillsize);
        y2 = y1 + hillsize/ 8 + IRND(hillsize-hillsize/ 8);

        if((x1<=GridDim/2-4 && x2>=GridDim/2-4) || 
           (x1<=GridDim/2+4 && x2>=GridDim/2+4))
        {
            x1 = IRND(2)-2 + GridDim/2;
            x2 = x1 + IRND(GridDim/2 - x1 + 2);
        }

        /* make a sinusoidal hill */
        for (r = x1; r < x2; r++)
            for (c = y1; c < y2; c++) {
                A[r * GridDim + c] +=.35 *
                    (sinf(M_PI * (float) (r - x1) / (float) (x2 - x1)) *
					 (sinf(M_PI * (float) (c - y1) / (float) (y2 - y1))));
            }
    }

    /* clamp the elevation of the terrain */
	for (r = 1; r < GridDim; r++)
		for (c = 1; c < GridDim; c++) {
			A[r * GridDim + c] = MIN(A[r * GridDim + c], .95);
			A[r * GridDim + c] = MAX(A[r * GridDim + c], 0.);
		}

}

#define NUM_DROPS                       80

static void erode_terrain(void)
{
    float           x, y, xv, yv, dx, dy;
    float           cut, min, take;
    int             nm;
    int             t, xi, yi, xo, yo, done;
    int             ii, jj, r, c;

    for (nm = 1; nm < NUM_DROPS*GridDim; nm++) {

        /* find a random position to start the 'rain drop' */
        x = (float) (IRND(GridDim));
        y = (float) (IRND(GridDim));

        /* Clamp x and y to be inside grid */
        x = MIN(MAX(2., x), (float)GridDim-2.);
        y = MIN(MAX(2., y), (float)GridDim-2.);
        
        done = 0;
        yv = xv = 0.;

        t = 0;
        cut = .3;

        while (!done) {
            xi = (int) x;
            yi = (int) y;

            min = 90.;

			if (xi != xo || yi != yo) {
                cut *=.99;

                /* gradient */
                dx = (A[(xi + 1)*GridDim + yi] - A[(xi - 1) * GridDim + yi]);
                dy = (A[xi * GridDim + yi + 1] - A[xi * GridDim + yi - 1]);


                /* find lowest neighbor */
                for (ii = -1; ii <= 1; ii++)
                    for (jj = -1; jj <= 1; jj++)
                        if (A[(xi + ii)*GridDim + yi + jj] < min)
                            min = A[(xi + ii)*GridDim + yi + jj];

                /* evaporate drop if sitting on my old location */
                if (M[xi][yi] == nm)
					done = 1;
                M[xi][yi] = nm;

                /* cave in neighbors by .3  */
                for (ii = -1; ii <= 1; ii++)
                    for (jj = -1; jj <= 1; jj++) {
                        take =.3 * cut * (A[(xi + ii)*GridDim + yi + jj]-min);
                        A[(xi + ii)*GridDim + yi + jj] -= take;
                    }

                /* take away from this cell by .7 */
                take = (A[xi*GridDim + yi] - min) *.7 * cut;
                A[xi*GridDim + yi] -= take;

            }
            xo = xi;
            yo = yi;

            /* move drop using kinematic motion*/
            xv = xv - dx -.8 * xv;
            yv = yv - dy -.8 * yv;

            x += xv;
            y += yv;

            /*
             * make sure can't move by more that 1.0
             * in any direction 
             */

            xv = MAX(xv, -1);
            yv = MAX(yv, -1);
            xv = MIN(xv,  1);
            yv = MIN(yv,  1);

            /* check to see if need a new drop */
            /* ie ran of world, got stuck, or at 'sea level' */
            if (x < 1.|| x > GridDim - 1.|| y < 1.|| y > GridDim - 1.
                                                || t++ > 2000
                || cut <.01)
                done = 1;

            if (A[xi*GridDim + yi] < 0.0001) {
                A[xi*GridDim + yi] = 0.;
                done = 1;
            }
		}                       /* while (!done) with this drop */
	}                           /* next drop */

	/*
	 *  invert the pseudo hill int the pseudo canyon
	 */

	for (r = 0; r < GridDim; r++)
		for (c = 0; c < GridDim; c++)
			 if(r>=GridDim/2-4 && r<=GridDim/2+4)
				A[r * GridDim + c] = MAX((-3.2 * A[r * GridDim + c]), -1.8);
}

static void color_terrain(void)
{
    float           N[3], D, alt, maxelev = -1.;
    int             x, y;

    for (x = 0; x < GridDim; x++)
        for (y = 0; y < GridDim; y++)
            maxelev = MAX(maxelev, A[x * GridDim + y]);

    for (x = 1; x < GridDim - 1; x++)
        for (y = 1; y < GridDim - 1; y++) {
            alt = A[x * GridDim + y] * 1.5;
            /* randomly perterb to get a mottling effect */
            alt += IRND(100) / 400. -.125;
            alt = MIN(alt, 1.0);
            if (alt < -.11) {
                C[x][y][0] = 0.6;         /* soil/rock in canyon */
                C[x][y][1] = 0.5;
                C[x][y][2] = 0.2;
            } else if (alt < .000001) {
                C[x][y][0] = 0.0;         /* dark, jungle lowlands */
                C[x][y][1] = 0.2;
                C[x][y][2] = 0.05;
            } else if (alt <.90) {
                C[x][y][0] = alt*.25;     /* green to redish hillsides */
                C[x][y][1] = (1.0 - alt) *.4 + .1;
                C[x][y][2] = 0.1;
            } else {
                C[x][y][0] = alt;
                C[x][y][1] = alt;         /* incresingly white snow */
                C[x][y][2] = alt;
            }

            /* compute normal to terrain */

            N[0] = A[(x - 1)*GridDim + y] - A[(x + 1)*GridDim + y];
            N[1] = A[x*GridDim + y - 1] - A[x*GridDim + y + 1];
            N[2] = 2.0 / ScaleZ;

            D = 1.0 / sqrtf(N[0] * N[0] + N[1] * N[1] + N[2] * N[2]);

            N[0] *= D;
            N[1] *= D;
            N[2] *= D;

            /* perform diffuse lighting of terrain */

            D = N[0] * LX + N[1] * LY + N[2] * LZ;
            D *= 1.2;

            if(!IRND(4)) D *= .5;
        
            D = MAX(D,0);

            /* darken terrain on shaded side */
            C[x][y][0] *= D;
            C[x][y][1] *= D;
            C[x][y][2] *= D;

            S[x][y] = (float) (x) / (float)CellDim;
            T[x][y] = (float) (y) / (float)CellDim;

        }
}

/*
 * create perobj to hold a cell of terrain
 * for a 5x5 terrain cell, there will be 5
 * tmeshes each with 10 triangles (12 vertexes)
 * this means looking past my cell to neighbors
 * to stitch things together.  To keep data contigious,
 * vertexes are repeated in tmeshes, not shared.
 */

static void init_cells(void)
{
    int             x, y, xx, yy, world_x, world_y;
    int             pntr, index, sstart, tstart;
    float           *D;
    perfobj_t       *pobj;

    D = (float*)calloc(CellDim *(CellDim + 1) * 
                        2 * FLOATS_PER_VERT_PAIR, sizeof(float));

    pobj = &(SharedData->terrain_texture_obj);
    pobj->flags = SharedData->terrain_texture_flags;

    put_texture_bind(1, pobj);

    for (x = 0; x < NumCells; x++)
        for (y = 0; y < NumCells; y++) {
			index = x*NumCells+y;
            SharedData->terrain_cells[index].flags = 
                SharedData->terrain_cell_flags[index];

            SharedData->terrain_cells[index].vdata =
                (float *) SharedData->terrain_cell_verts[index];

            pntr = 0;

            /*
             * Guarantee S,T to be within range 0-8 for tmesh strip using
             * 256x256 sized texture. This avoids texture index clipping
             * in pipe. 
            */
            sstart = (int)S[x*CellDim][y*CellDim];
            tstart = (int)T[x*CellDim][y*CellDim];

            for (xx = 0; xx < CellDim; xx++)
                for (yy = 0; yy < CellDim + 1; yy++) {

                    /* init a perfobj */

                    world_x = MIN(x * CellDim + xx, GridDim-2);
                    world_y = MIN(y * CellDim + yy, GridDim-2);

                    D[pntr + 0] = S[world_x][world_y] - sstart;
                    D[pntr + 1] = T[world_x][world_y] - tstart;
                    D[pntr + 2] = C[world_x][world_y][0];
                    D[pntr + 3] = C[world_x][world_y][1];
					D[pntr + 4] = C[world_x][world_y][2];
                    D[pntr + 5] = (float)world_x * XYScale;
                    D[pntr + 6] = (float)world_y * XYScale;
					D[pntr + 7] = A[world_x*GridDim + world_y]*ScaleZ;


                    D[pntr + 8] = S[world_x + 1][world_y] - sstart;
                    D[pntr + 9] = T[world_x + 1][world_y] - tstart;
                    D[pntr + 10] = C[world_x + 1][world_y][0];
                    D[pntr + 11] = C[world_x + 1][world_y][1];
                    D[pntr + 12] = C[world_x + 1][world_y][2];
                    D[pntr + 13] = (float)(world_x+1) * XYScale;
                    D[pntr + 14] = (float)world_y * XYScale;
                    D[pntr + 15] = A[(world_x+1)*GridDim + world_y] * ScaleZ;

                    pntr += FLOATS_PER_VERT_PAIR;

                }               /* for each cell */
            put_cell(D, &(SharedData->terrain_cells[index]));

        }                       /* for all cells in world */

        free(D);
}

static void put_cell(float *source, perfobj_t *pobj)
{
	int             i, j;
	perfobj_vert_t *pdataptr =(perfobj_vert_t *) pobj->vdata;
	unsigned long  *flagsptr = pobj->flags;
	float          *sp = source;

	/* For all tmesh strips in cell */
	for (i = 0; i < CellDim; i++) {
		*flagsptr++ = PD_DRAW_TERRAIN_CELL;
		*flagsptr++ = (unsigned long) pdataptr;

		/* For all verts in tmesh strip */
		for (j = 0; j < (CellDim + 1) * 2; j++) {
			putt2fdata(sp, pdataptr);
			putc3fdata(sp + 2, pdataptr);
            putv3fdata(sp + 5, pdataptr);

            sp += 8;
            pdataptr++;
        }
    }
    *flagsptr++ = PD_END;
}

