#include "../include/zf.h"

/* must be ^2 to fit into the texture height */
#define RENDER_STRIP_HEIGHT 32 
#define STRIP_RANGE 8

#define FILENAME_STRING_LENGTH 80

/*!
  START - clubox

  \todo put this in clu.h and clubox.c
*/
typedef struct CLUbox CLUbox;

struct CLUbox
{
    CLvertex min;
    CLvertex max;
};

GLfloat cluBoxWidth(const CLUbox* box)
{
    return box->max.x - box->min.x;
}

GLfloat cluBoxHeight(const CLUbox* box)
{
    return box->max.y - box->min.y;
}

GLfloat cluBoxDepth(const CLUbox* box)
{
    return box->max.z - box->min.z;
}

CLvertex* cluBoxOrigin(CLvertex* v, const CLUbox* box)
{
    /*    return cluSetVertex(v, 0.0f, 0.0f, 0.0f);*/
    
    return cluSetVertex(v,
			(box->min.x + box->max.x) / 2.0f,
			(box->min.y + box->max.y) / 2.0f,
			(box->min.z + box->max.z) / 2.0f);
    
}

/*
  END - clubox
*/

struct ZfHeightmap
{
    char heights_filename[FILENAME_STRING_LENGTH];
    char colours_filename[FILENAME_STRING_LENGTH];
    char detail_texture_filename[FILENAME_STRING_LENGTH];
    
    CLUbox box; /* box to fit this heightmap into */
    float water_level;
};
   
ZfHeightmap* heightmap;

/*
  runtime data
*/
static float width;
static float height;
static float depth;
static CLnormal offset;

static float red_scale;
static float green_scale;
static float blue_scale;

/*
  render state variables
*/
static bool wire_mode;           /* heightmap is drawn as wireframe */

/* due to big hack, heights and colours must be same dimensions */
static CLimage* heights_image; /* could delete these for runtime? */
static CLimage* colours_image; /* will they be required by the tool? */
static CLtexture* detail_texture;

static GLfloat* heights;
static CLcolour* colours; /* should probably change to ubyte rgb for space! */
static CLnormal* normals;
    
static GLuint display_list;
static int number_of_display_lists;

static bool loaded = false; /* false until a heightmap file is loaded, state is queried by zf_heightmap_loaded() */

/*!
  \brief Calculates map coordinates given world coordinate
*/
static void
world_to_map(float *u, float *v, float x, float z)
{
    *u = (x - (offset.i - width / 2.0f))
	/ (width / heights_image->width);

    *v = (z - (offset.k - depth / 2.0f))
	/ (depth / heights_image->height);
}

/*!
  \brief Calculates map coordinates given world coordinate
*/
static void
map_to_world(float *x, float* z, float u, float v)
{
    *x = (u * (width / heights_image->width))
        + (offset.i - width / 2.0f);

    *z = (v * (depth / heights_image->height))
        + offset.k - depth / 2.0f;
}

static GLfloat
get_height(unsigned int u, unsigned int v)
{
    /* check u in range */
    if (u >= heights_image->width)
    {
	/*
	  printf("%s(%u, %u) : ERROR : heights_image->width exceeded, clamping to %u\n",
	  __FUNCTION__,
	  u,
	  v,
	  heights_image->width - 1);
	*/
	
	u = heights_image->width - 1;
    }

    /* check v in range */
    if (v >= heights_image->height)
    {
	/*
	  printf("%s(%u, %u) : ERROR : heights_image->height exceeded, clamping to %u\n",
	  __FUNCTION__,
	  u,
	  v,
	  heights_image->height - 1);
	*/
	
	v = heights_image->height - 1;
    }
    

    return heights[u + v * heights_image->width];
}

static CLcolour*
get_colour(unsigned int u, unsigned int v)
{
    /* check u in range */
    if (u >= heights_image->width)
    {
	u = heights_image->width - 1;
    }
    
    /* check v in range */
    if (v >= heights_image->height)
    {
	v = heights_image->height - 1;
    }

    return colours + (u + v * heights_image->width);
}

static CLnormal*
get_normal(unsigned int u, unsigned int v)
{
    /* check u in range */
    if (u >= heights_image->width)
    {
	u = heights_image->width - 1;
    }
    
    /* check v in range */
    if (v >= heights_image->height)
    {
	v = heights_image->height - 1;
    }

    return normals + (u + v * heights_image->width);
}

static void
query_vertex(CLvertex* vertex, unsigned int u, unsigned int v)
{
    GLfloat x;
    GLfloat y;
    GLfloat z;

    map_to_world(&x, &z, (float)u, (float)v);
	
    y = get_height(u, v);

    cluSetVertex(vertex, x, y, z);

#if 0
    clPrintVertex(vertex);
#endif
}

static void
query_texcoord(CLtexcoord* texcoord, unsigned int u, unsigned int v)
{
    /* haX0r! should use detail_texture_repeat or something similar! */
    cluSetTexCoord(texcoord,
                   (GLfloat)u 
                   / (GLfloat)heights_image->width * 32.0f,
                   (GLfloat)v 
                   / (GLfloat)heights_image->height * 32.0f);
}

static GLfloat
create_height(GLfloat r, GLfloat g, GLfloat b)
{
    return 
        ((((r / 255.0f) * red_scale) +
          ((g / 255.0f) * green_scale) +
          ((b / 255.0f) * blue_scale)) * height)
        + offset.j;
}

static void
create_heights(void)
{
    unsigned int i;
    unsigned int num_heights;
    GLubyte* data;
    unsigned int pixel_size;

    /* calc number of height values defined by the heights image */
    num_heights = heights_image->width * heights_image->height;
    
    /* malloc an array to hold the heights once calculated */
    heights = g_new(GLfloat, num_heights);
    
    /* get fast pointer to heights image data */
    data = (GLubyte*)heights_image->data;

    /* get the pixel size from the image */
    pixel_size = clImagePixelSize(heights_image);

#if 0
    /* debug */
    clPrintImage(heights_image);

    printf("clImageNumPixels = %u\n", clImageNumPixels(heights_image));
    printf("clImagePixelSize = %u\n", clImagePixelSize(heights_image));
    printf("clImageNumElements = %u\n", clImageNumElements(heights_image));
    printf("clImageElementSize = %u\n", clImageElementSize(heights_image));
    printf("clImageDataSize = %u\n", clImageDataSize(heights_image));

    /* end debug */
#endif

    /* calculate the heights */
    for (i = 0; i < num_heights; i++)
    {
        heights[i] = create_height((GLfloat)(data[pixel_size * i]),
				   (GLfloat)(data[pixel_size * i + 1]),
				   (GLfloat)(data[pixel_size * i + 2]));
    }
}

static void
create_colours(void)
{
    unsigned int i;
    unsigned int num_colours;
    GLubyte*     data;
    bool         has_alpha_channel;

    num_colours = colours_image->width * colours_image->height;

    colours = g_new(CLcolour, num_colours);

    data = (GLubyte*)colours_image->data;

    has_alpha_channel = (clImagePixelSize(colours_image) > 3);

#if 0
    /* debug */
    clPrintImage(colours_image);
    
    printf("clImageNumPixels = %u\n", clImageNumPixels(colours_image));
    printf("clImagePixelSize = %u\n", clImagePixelSize(colours_image));
    printf("clImageNumElements = %u\n", clImageNumElements(colours_image));
    printf("clImageElementSize = %u\n", clImageElementSize(colours_image));
    printf("clImageDataSize = %u\n", clImageDataSize(colours_image));

    /* end debug */
#endif

    for (i = 0; i < num_colours; i++)
    {
	if (has_alpha_channel)
	{
	    cluSetColour(colours + i,
			 ((GLfloat)(data[4 * i])) / 255.0f,
			 ((GLfloat)(data[4 * i + 1])) / 255.0f,
			 ((GLfloat)(data[4 * i + 2])) / 255.0f,
			 ((GLfloat)(data[4 * i + 3])) / 255.0f);
	}
	else
	{
	    cluSetColour(colours + i,
			 ((GLfloat)(data[3 * i])) / 255.0f,
			 ((GLfloat)(data[3 * i + 1])) / 255.0f,
			 ((GLfloat)(data[3 * i + 2])) / 255.0f,
			 1.0f);
	}
    }
}

static void
create_normals(void)
{
    unsigned int i;
    unsigned int j;
    unsigned int n;

    CLvertex v0;
    CLvertex v1;
    CLvertex v2;
    CLvertex v3;
    CLnormal n0;
    CLnormal n1;
    CLnormal n2;
    CLnormal n3;

    n = heights_image->width * heights_image->height;

    normals = g_new(CLnormal, n);

    for (i = 0; i < n; i++)
        cluSetNormal(normals + i, 0.0f, 0.0f, 0.0f);

    for (i = 0; i < heights_image->width - 1; i++)
        for (j = 0; j < heights_image->height - 1; j++)
        {
            query_vertex(&v0, i, j);
            query_vertex(&v3, i, j + 1);
            query_vertex(&v2, i + 1, j + 1);
            query_vertex(&v1, i + 1, j);

            cluSetNormalTriangle(&n0, &v0, &v1, &v2);
            cluSetNormalTriangle(&n1, &v2, &v3, &v0);
            cluSetNormalTriangle(&n2, &v0, &v1, &v3);
            cluSetNormalTriangle(&n3, &v1, &v2, &v3);

            cluNormalAdd(&n0, &n1);
            cluNormalAdd(&n0, &n2);
            cluNormalAdd(&n0, &n3);

            cluNormalAdd(get_normal(i, j), &n0);
            cluNormalAdd(get_normal(i, j + 1), &n0);
            cluNormalAdd(get_normal(i + 1, j + 1), &n0);
            cluNormalAdd(get_normal(i + 1, j), &n0);
        }

    for (i = 0; i < n; i++)
    {
        cluNormalNormalise(normals + i);

        /* hax0r - the normals should be fixed to use other
           orientation, but i am just inverting them here for now */
        cluNormalScale(normals + i, -1.0f);
    }
}

static void
create_display_list(void)
{
    unsigned int i;
    unsigned int j;
    unsigned int ln;  /* list number */

    CLvertex vertex;
    CLtexcoord texcoord;
    
    /* break the map into 128xRENDER_STRIP_HEIGHT strips to display lists*/
    number_of_display_lists = heights_image->height / RENDER_STRIP_HEIGHT;

    display_list = glGenLists(number_of_display_lists);
 
    /* haxor */
    glColor3f(0.8f, 0.8f, 0.8f);
    
    for(ln = 0; ln < number_of_display_lists; ln++)
    {
	glNewList(display_list+ln, GL_COMPILE);

	glBegin(GL_TRIANGLES);
	/* glBegin(GL_LINES); */

	for (i = 0; i < heights_image->width - 1; i++)
	{
	    //printf("i = %u\n", i);
	    for (j = RENDER_STRIP_HEIGHT*ln; j < RENDER_STRIP_HEIGHT*(ln+1) && j < heights_image->height - 1; j++)
	    {
		//printf("j = %u\n", j);

		glNormal3fv((GLfloat*)get_normal(i, j));
		glColor4fv((GLfloat*)get_colour(i, j));
		query_texcoord(&texcoord, i, j);
		glTexCoord2fv((GLfloat*)&texcoord);
		query_vertex(&vertex, i, j);
		glVertex3fv((GLfloat*)&vertex);
            
		//printf("a\n");

		glNormal3fv((GLfloat*)get_normal(i + 1, j + 1));
		glColor4fv((GLfloat*)get_colour(i + 1, j + 1));
		query_texcoord(&texcoord, i + 1, j + 1);
		glTexCoord2fv((GLfloat*)&texcoord);
		query_vertex(&vertex, i + 1, j + 1);
		glVertex3fv((GLfloat*)&vertex);

		//printf("b\n");

		glNormal3fv((GLfloat*)get_normal(i + 1, j));
		glColor4fv((GLfloat*)get_colour(i + 1, j));
		query_texcoord(&texcoord, i + 1, j);
		glTexCoord2fv((GLfloat*)&texcoord);
		query_vertex(&vertex, i + 1, j);
		glVertex3fv((GLfloat*)&vertex);

		glNormal3fv((GLfloat*)get_normal(i, j));
		glColor4fv((GLfloat*)get_colour(i, j));
		query_texcoord(&texcoord, i, j);
		glTexCoord2fv((GLfloat*)&texcoord);
		query_vertex(&vertex, i, j);
		glVertex3fv((GLfloat*)&vertex);

		glNormal3fv((GLfloat*)get_normal(i, j + 1));
		glColor4fv((GLfloat*)get_colour(i, j + 1));
		query_texcoord(&texcoord, i, j + 1);
		glTexCoord2fv((GLfloat*)&texcoord);
		query_vertex(&vertex, i, j + 1);
		glVertex3fv((GLfloat*)&vertex);

		glNormal3fv((GLfloat*)get_normal(i + 1, j + 1));
		glColor4fv((GLfloat*)get_colour(i + 1, j + 1));
		query_texcoord(&texcoord, i + 1, j + 1);
		glTexCoord2fv((GLfloat*)&texcoord);
		query_vertex(&vertex, i + 1, j + 1);
		glVertex3fv((GLfloat*)&vertex);
	    }
	}
	glEnd();
	glEndList();
    }
   
}

static void
create_runtime_variables(void)
{
    //printf("%s() : START\n", __FUNCTION__);

    /* set width, height, and depth from bounding box */
    width  = cluBoxWidth (&heightmap->box);
    height = cluBoxHeight(&heightmap->box);
    depth  = cluBoxDepth (&heightmap->box);

    /* set offset from bounding box */
    cluBoxOrigin((CLvertex*)&offset, &heightmap->box);

    blue_scale = 
        height / (255.0f 
		  + 255.0f * 255.0f
		  + 255.0f * 255.0f * 255.0f);
    green_scale = 255.0f * blue_scale;
    red_scale = 255.0f * green_scale;
    
#if 0
    printf("%f %f %f %f %f\n",
           red_scale,
           green_scale,
           blue_scale,
           height,
           255.0f * red_scale
           + 255.0f * green_scale
           + 255.0f * blue_scale);
#endif

    /* load heights image */
    //printf("%s() : loading heights %s\n", __FUNCTION__, heightmap->heights_filename);
    heights_image = clioLoadImage(heightmap->heights_filename);

    if (!heights_image)
    {
        printf("%s() : ERROR : could not load heights image for map\n", __FUNCTION__);
    }

    /* load colours image */
    //printf("%s() : loading colours %s\n", __FUNCTION__, heightmap->colours_filename);
    colours_image = clioLoadImage(heightmap->colours_filename);
    
    if (!colours_image)
    {
	printf("%s() : ERROR : could not load colours image for map\n", __FUNCTION__);
    }

    /* load detail texture */
    //printf("%s() : loading detail texture %s\n", __FUNCTION__, heightmap->detail_texture_filename);
    detail_texture = clioLoadTexture(heightmap->detail_texture_filename);
    
    if (!detail_texture)
    {
        printf("%s() : ERROR : could not load detail texture for map\n", __FUNCTION__);
    }
  
    //printf("%s() : update detail texture\n", __FUNCTION__);
    clUpdateTexture(detail_texture);

    /* create heights, normals, and display list */
    //printf("%s() : create!\n", __FUNCTION__);
    create_heights();
    //printf("%s() : a\n", __FUNCTION__);
    create_colours();
    //printf("%s() : b\n", __FUNCTION__);
    create_normals();
    //printf("%s() : c\n", __FUNCTION__);
    create_display_list();

    //printf("%s() : END\n", __FUNCTION__);
}

static void
init(void)
{
    width = 0;
    height = 0;

    heights = 0;
    colours = 0;
    normals = 0;

    wire_mode = false;

    display_list = 0;
}

static void
render(void* move_along)
{
    unsigned int ln; /* list number */

    glPushAttrib(GL_ALL_ATTRIB_BITS); /* GL_LIGHTING_BIT | GL_CURRENT_BIT | GL_ENABLE_BIT); */

    glEnable(GL_COLOR_MATERIAL);
    glColor3f(1.0f, 1.0f, 1.0f);
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_LIGHTING);

    clLoadTexture(detail_texture);
    
    /* wire frame mode */
    if(wire_mode)
    {
	glDisable(GL_LIGHTING);
	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	glDisable(GL_CULL_FACE);
    }
    else
    {
	glEnable(GL_CULL_FACE);
    }   
	
    
    // nickl - hack to get
    //glDisable(GL_ALPHA_TEST);
    
    if(zf_render_system_get_tool_mode())
    {
	for(ln = 0; ln < number_of_display_lists; ln++)
	{
	    glCallList(display_list+ln);
	}
    }
    else
    {
	/* get the ship position to know which lists to render */
	{
	    CLvertex position;
	    float v; /* don't need u */
	    float temp;
	    int counter = 0;
	    int range_limit;
	    
	    zf_ship_query_position(&position);
	    
	    v = (position.z - (offset.k - depth / 2.0f))
		/ (depth / heights_image->height);
	    
	    /* find which strip the ship is on*/
	    while(1)
	    {
		temp = (float)(counter * RENDER_STRIP_HEIGHT);
		if( v < temp)
		{
		    counter--;
		    break;
		}
		counter++;
	    }
	    
	    if(!zf_camera_query_boss_mode())
	    {
		/* get the strips STRIP_RANGE ahead of the ship to render*/
		range_limit = counter + STRIP_RANGE;
		
		/* in order to draw the strip behind the ship */
		if(counter > 0)
		    counter--;
		
		if(range_limit >= number_of_display_lists)
		    range_limit = number_of_display_lists-1;
	    }
	    else
	    {
		/* get the strips STRIP_RANGE ahead of the ship to render*/
		range_limit = counter + STRIP_RANGE / 2;
		
		/* in order to draw the strip behind the ship */
		counter -= STRIP_RANGE / 2;

		if(counter < 0)
		    counter = 0;
		
		if(range_limit >= number_of_display_lists)
		    range_limit = number_of_display_lists-1;
	    }
	    
	    for(ln = counter; ln < range_limit; ln++)
		glCallList(display_list+ln);
	}
    }

#if 0
    /* render axes */
    {
	glLineWidth(5.0f);
	glBegin(GL_LINES);
	
	/* red */
	glColor3f(1.0f, 0.0f, 0.0f);
	glVertex3f(0.0f, 0.0f, 0.0f);
	glVertex3f(100.0f, 0.0f, 0.0f);

	/* green */
	glColor3f(0.0f, 1.0f, 0.0f);
	glVertex3f(0.0f, 0.0f, 0.0f);
	glVertex3f(0.0f, 100.0f, 0.0f);

	/* blue */
	glColor3f(0.0f, 0.0f, 1.0f);
	glVertex3f(0.0f, 0.0f, 0.0f);
	glVertex3f(0.0f, 0.0f, 100.0f);
	
	glEnd();

	glLineWidth(1.0f);
    }
#endif

#if 0 /* render "thumping" wireframe over heightmap */
    {
	glPushMatrix();
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

	glColor3f(1.0f, 1.0f, 1.0f);

	for(ln = 0; ln < number_of_display_lists; ln++)
	{
	    glCallList(display_list+ln);
	}

	glPopMatrix();

	glDisable(GL_LIGHTING);
	glDisable(GL_TEXTURE_2D);
	glEnable(GL_BLEND);
	/* glBlendFunc(GL_ONE, GL_ONE); */
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glLineWidth(1.0f);
	glEnable(GL_LINE_SMOOTH);
	glDisable(GL_ALPHA_TEST);

	glPushMatrix();
	glTranslatef(0.0f, 2.0f * *zf_audio_get_spectrum(), 0.0f);
	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	//glCallList(display_list);
	//glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	//glPopMatrix();

	//glCallList(display_list);

	glColor4f(1.0f, 0.0f, 0.0f, 0.5f);

	for(ln = 0; ln < number_of_display_lists; ln++)
	{
	    glCallList(display_list+ln);
	}

	glPopMatrix();
    }
#endif

    glPopAttrib();
}

static void
write(FILE* stream, ZfHeightmap* h)
{
    /* to do */
    /* this current HACK! assumes that the images are saved already */
    fprintf(stream, "ZfHeightMap\n{\n");
    
    fprintf(stream, "heights_filename = %s\n",
            h->heights_filename);

    fprintf(stream, "colours_filename = %s\n",
            h->colours_filename);

    fprintf(stream, "detail_texture_filename = %s\n",
            h->detail_texture_filename);
    
    fprintf(stream, "box =\n{\n");

    fprintf(stream, "min = %f %f %f\n",
            h->box.min.x,
            h->box.min.y,
            h->box.min.z);

    fprintf(stream, "max = %f %f %f\n",
            h->box.max.x,
            h->box.max.y,
            h->box.max.z);

    fprintf(stream, "water_level = %f\n", h->water_level);

    fprintf(stream, "}\n");

    fprintf(stream, "}\n");
}

/*!
  \todo hack number. should be in the heightmap file eventually
*/
static void
read(ZfHeightmap* h, FILE* stream)
{
    float water_level;

    fscanf(stream, "ZfHeightMap\n{\n");
    
    fscanf(stream, "heights_filename = %s\n",
           h->heights_filename);

    fscanf(stream, "colours_filename = %s\n",
           h->colours_filename);

    fscanf(stream, "detail_texture_filename = %s\n",
           h->detail_texture_filename);
    
    fscanf(stream, "box =\n{\n");

    fscanf(stream, "min = %f %f %f\n",
           &h->box.min.x,
           &h->box.min.y,
           &h->box.min.z);

    fscanf(stream, "max = %f %f %f\n",
           &h->box.max.x,
           &h->box.max.y,
           &h->box.max.z);

    fscanf(stream, "water_level = %f\n", &water_level);

    fscanf(stream, "}\n");
   
    fscanf(stream, "}\n");

    create_runtime_variables();

    //printf("%s() : loading water\n", __FUNCTION__);
    zf_water_init(water_level, width, depth);
}

/*!
  \todo Docs. What is (dt > ds) doing?
*/
float
zf_heightmap_query_height(float x, float z)
{
    float s; /* should change these var names */
    float t;

    unsigned int u;  /* should change these var names */
    unsigned int v;
  
    float ds;
    float dt;

    float h0;
    float h1;

    world_to_map(&s, &t, x, z);

    u = (unsigned int)floor(s);
    v = (unsigned int)floor(t);
  
    ds = s - u;
    dt = t - v;


    /*
      perform bounds checking (haxor - put this back in later!)
    */
    if ((u < 0) || (u >= heights_image->width))
	return 0.0f;
  
    if ((v < 0) || (v >= heights_image->height))
	return 0.0f;

    /* per-triangle bi-linear interpolation */
    if (dt > ds)
    {
	h0 = 
	    (1.0f - dt) * get_height(u, v) 
	    + dt * get_height(u, (v + 1));
 
	h1 =
	    (1.0f - dt) * get_height(u, v)
	    + dt * get_height((u + 1), (v + 1));

	return (1.0f - (ds / dt)) * h0 + (ds / dt) * h1;
    }
    else
    {
	float d;

	h0 = 
	    (1.0f - dt) * get_height(u, v)
	    + dt * get_height((u + 1), (v + 1));

	h1 = 
	    (1.0f - dt) * get_height((u + 1), v)
	    + dt * get_height((u + 1), (v + 1));
    
	d = (ds - dt) / (1.0f - dt);

	return (1.0f - d) * h0 + d * h1;
    }
}

static void
query_normal(float x, float z,
	     CLnormal* normal)
{
    float s; /* should change these var names */
    float t;

    unsigned int u;  /* should change these var names */
    unsigned int v;
  
    float ds;
    float dt;

    float omds; /* one minus ds */
    float omdt; /* one minus dt */

    CLnormal n0;
    CLnormal n1;
    CLnormal n;

    world_to_map(&s, &t, x, z);

    u = (unsigned int)floor(s);
    v = (unsigned int)floor(t);
  
    ds = s - u;
    dt = t - v;


    /*
      perform bounds checking (haxor - put this back in later!)
    */
    if ((u < 0) || (u >= heights_image->width))
    {
	cluSetNormal(normal, 0.0f, 1.0f, 0.0f);
	return;
    }
  
    if ((v < 0) || (v >= heights_image->height))
    {
	cluSetNormal(normal, 0.0f, 1.0f, 0.0f);
	return;
    }

    /* per-quad bilinear interpolation */

    omds = 1.0f - ds;
    omdt = 1.0f - dt;
    
    cluNormalAdd(cluNormalScale(clCopyNormal(&n0, get_normal(u, v)), omdt),
		 cluNormalScale(clCopyNormal(&n, get_normal(u, v + 1)), dt));
    
    cluNormalAdd(cluNormalScale(clCopyNormal(&n1, get_normal(u + 1, v)), omdt),
		 cluNormalScale(clCopyNormal(&n, get_normal(u + 1, v + 1)), dt));
    
    cluNormalAdd(cluNormalScale(&n0, omds),
		 cluNormalScale(&n1, ds));
    
    clCopyNormal(normal, &n0);
}

void
zf_heightmap_query_normal(CLnormal* result, float x, float z)
{
    query_normal(x, z, result);
}

/*
  STUFF FOR NEW ZF (THE ONE TRUE PATH!!!!... for code)
*/

/* this doesn't really do anything because there is only ever one instance! */

/* these will be the same for all singletons - maybe provide standard
   "empty" functions - MAYBE EVEN A STANDARD zf_static_smart_pointer!
   (OMFG!) */
static bool
is_valid(const void* nothing_to_see_here)
{
    return true;
}

static void
reference(void* nothing_to_see_here)
{
}

static void
release(void* nothing_to_see_here)
{
}
/* end of stuff we don't really need */

/* be very careful about setting friction/restitution too high (must
   be less than 1.0 */ 
static void
bounce_particle(const CLnormal* velocity,
		CLvertex* point, /* of intersection - this may be offset */
		const CLnormal* normal, /* at intersection point */
		float friction,
		float restitution,
		CLnormal* force_perp,
		CLnormal* force_tan)
{
    GLfloat dot;

    /* generate normal component */
    dot = cluNormalDotProduct(normal, velocity);
    clCopyNormal(force_perp, normal);
    cluNormalScale(force_perp, dot);

    /* HACK - offset collision position slightly */
    {
	CLnormal offset;
	
	clCopyNormal(&offset, force_perp);
	cluNormalScale(&offset, -friction); /* magic numbers! */
	cluVertexAdd(point, &offset);
    }

    /* generate tangential component */
    clCopyNormal(force_tan, velocity);
    cluNormalSubtract(force_tan, force_perp);
    
    /* generate reflection vector */
    cluNormalScale(force_tan, friction);
    cluNormalScale(force_perp, -restitution);

    /* get reflection forces relative to input velocity */
    {
	CLnormal velocity_normal;
	CLnormal reflection;

	clCopyNormal(&velocity_normal, velocity);
	cluNormalNormalise(&velocity_normal);

	clCopyNormal(&reflection, force_perp);
	cluNormalAdd(&reflection, force_tan);

	cluNormalCrossProduct(force_tan, &reflection, &velocity_normal);

	clCopyNormal(force_perp, &reflection);
	cluNormalSubtract(force_perp, force_tan);
    }
}

static bool
query_collision(void* there_is_no_spoon,
		const CLUsphere* sphere,
		const CLnormal* force,
		ZfType collider_type,
		CLvertex* collision_position,
		CLnormal* collision_force_perp,
		CLnormal* collision_force_tan)
{
    float height;

    height = zf_heightmap_query_height(sphere->origin.x, sphere->origin.z);

    if (sphere->origin.y < height + sphere->radius)
    {
	clCopyVertex(collision_position, &sphere->origin);
	collision_position->y = height + sphere->radius;

	/* TODO - calculate force based on input force, friction, and
	   stuff */
	/* cluSetNormal(collision_force, 0.0f, 0.0f, 0.0f); */
	{
	    CLnormal collision_normal;

	    query_normal(sphere->origin.x, sphere->origin.z,
			 &collision_normal);

	    bounce_particle(force,
			    collision_position,
			    &collision_normal,
			    0.9f,
			    0.1f,
			    collision_force_perp,
			    collision_force_tan);
	}
	return true;
    }

    return false;
}


void
zf_heightmap_set_wire_mode(bool wire)
{
    wire_mode = wire;
}

void
zf_heightmap_init(char* filename)
{
    static ZfSmartPointer smart_pointer;

    //printf("%s() : START\n", __FUNCTION__);

    smart_pointer.is_valid = is_valid;
    smart_pointer.reference = reference;
    smart_pointer.release = release;

    init(); /* should be in reference! and close() should be in release! */

    /* create the heightmap struct */
    heightmap = g_new(ZfHeightmap, 1);

    /* haxor! */
    {
	FILE* stream;
	
	stream = fopen(filename, "r");

	if (!stream)
	{
	    printf("%s() : ERROR : cant load heightmap file %s\n", __FUNCTION__, filename);
	    exit(1);
	}

	read(heightmap, stream);

	fclose(stream);
    }

    /* add heightmap to collision system as static object */
    zf_collision_system_add_static(0, /* null */
				   &smart_pointer,
				   query_collision,
				   ZF_GROUND);

    /* add heightmap to render system as opaque object */
    zf_render_system_add_opaque(0, /* null */
				&smart_pointer,
				render);

    /* a heightmap has been loaded, update state */
    loaded = true;

    //printf("%s() : END\n", __FUNCTION__);
}

void
zf_heightmap_close(void)
{   
    clDeleteImage(heights_image);
    clDeleteImage(colours_image);
    clDeleteTexture(detail_texture);
                  
    g_free(heights);
    g_free(colours);
    g_free(normals);

    g_free(heightmap);
    glDeleteLists(display_list, number_of_display_lists);
    glDisable(GL_FOG);

    zf_water_close();
}

/*!
  \brief Writes a heightmap.zf file using the supplied information. Information
  required are texture filename, colours filename and detail_texture_filename.
  Box is generated automatically. Heightmap is written to a file specified by
  output_filename.
*/
void
zf_heightmap_write_file(char* heights_filename,
			char* colours_filename,
			char* detail_texture_filename,
			char* output_filename,
			CLUalignedbox* box)
{
    ZfHeightmap* h;

    /* create heightmap struct */
    h = g_new(ZfHeightmap, 1);

    /* copy input strings */
    if (heights_filename)
    {
	strncpy(h->heights_filename, heights_filename, FILENAME_STRING_LENGTH);
    }
    
    if (colours_filename)
    {
	strncpy(h->colours_filename, colours_filename, FILENAME_STRING_LENGTH);
    }
    
    if (detail_texture_filename)
    {
	strncpy(h->detail_texture_filename, detail_texture_filename, FILENAME_STRING_LENGTH);
    }
    
    /* create the heightmap box */


    /* write the struct to the output file */
    {
	FILE* stream;

	stream = fopen(output_filename, "w");
	
	printf("%s() : write heightmap to %s\n",
	       __FUNCTION__,
	       output_filename);

	write(stream, h);

	fclose(stream);
    }
}

bool
zf_heightmap_loaded(void)
{
    return loaded;
}
