// <ZZ> This file contains functions for setting up objects, composed
//      of vertices and triangles...
//  object_destroy_all  - Resets all of the objects
//  object_start        - Starts building a new object
//	object_add_vertex	- Adds a vertex to the object at the specified location
//	object_add_triangle	- Adds a triangle to the object between the specified vertices
//	object_set_normal	- Sets the normal vector for a given vertex
//  object_set_texpos   - Sets the texture position for a given vertex
//	object_set_texture	- Sets the texture for the object
//	object_add_spring	- Adds a spring between two vertices
//	object_end          - Finishes building a new object
//  object_draw         - Draws the specified object
#define MAX_OBJECT 32
#define MAX_VERTEX 16384
#define MAX_TRIANGLE 16384
#define MAX_SPRING 32768
#define MAX_WELD 8192

#define WELD_TOLERANCE 0.0001f
#define INTER_OBJECT_WELD_TOLERANCE 0.01f
#define MAX_PRESS 40.0f

float global_object_scaling = 1.0f;
float global_dampen = 1.00f;
float global_toothpick_force = 1.00f;
float global_spring_force = 1.00f;
float global_return_force = 1.00f;

int error_vertex_count = 0;
int error_spring_count = 0;
int error_triangle_count = 0;

unsigned short num_object = 0;                                      // Number of objects
unsigned int object_texture[MAX_OBJECT];                            // Texture for each object
unsigned short object_num_vertex[MAX_OBJECT];                       // Number of vertices for each object
unsigned short object_num_triangle[MAX_OBJECT];                     // Number of triangles for each object
unsigned short object_num_spring[MAX_OBJECT];                       // Number of springs for each object
float object_vertex_data[MAX_OBJECT][MAX_VERTEX][22];               // xyz, Vxyz, Nxyz, Oxyz, Txy, LightXY, NormRGB, Press, Multiplier, WELDSTOP
unsigned short object_triangle_list[MAX_OBJECT][MAX_TRIANGLE][3];   // List of vertices to connect
float object_triangle_normal[MAX_OBJECT][MAX_TRIANGLE][3];          // List of triangle normals
float object_triangle_center_xyz[MAX_OBJECT][MAX_TRIANGLE][3];      // For optimizing collision detection
float object_triangle_bounding_radius[MAX_OBJECT][MAX_TRIANGLE];    // For optimizing collision detection
unsigned short object_spring_list[MAX_OBJECT][MAX_SPRING][2];       // List of vertices to connect
float object_spring_length[MAX_OBJECT][MAX_SPRING];                 // The distance between two springs
unsigned short object_num_weld[MAX_OBJECT];                         // The number of welded vertices
unsigned short object_weld_list[MAX_OBJECT][MAX_WELD][3];           // A list of which vertices to weld together
float object_center_xyz[MAX_OBJECT][3];                             // For optimizing collision detection
float object_bounding_radius[MAX_OBJECT];                           // For optimizing collision detection
float object_dampen[MAX_OBJECT];
float object_toothpick_force[MAX_OBJECT];
float object_spring_force[MAX_OBJECT];
float object_return_force[MAX_OBJECT];
float object_weight[MAX_OBJECT];



#define MAX_WEIGHT_HACK 100
unsigned short num_object_weight_hack = 0;
float object_weight_hack_xyz[MAX_WEIGHT_HACK][3];
float object_weight_hack_radius[MAX_WEIGHT_HACK];



unsigned short object_triangle_hit[MAX_OBJECT][MAX_TRIANGLE];        // Used for marking collisions



// Rendering modes
unsigned char object_draw_normal = FALSE;
float object_normal_scale = 1.00f;
unsigned char object_draw_wireframe = FALSE;
unsigned char object_draw_spring = FALSE;
unsigned char object_draw_hit = FALSE;
unsigned char object_generic_mode = FALSE;   // Flag for the generic airway model...

//------------------------------------------------------------------------------------
void object_destroy_all()
{
    // <ZZ> This function clears out all of the objects...
    unsigned short i;
    repeat(i, num_object)
    {
        log_message("Unloading texture for object %d", i);
        display_unload_texture(object_texture[i]);
    }
    repeat(i, MAX_OBJECT)
    {
        object_texture[i] = 0;
        object_num_vertex[i] = 0;
        object_num_triangle[i] = 0;
        object_num_spring[i] = 0;
        object_num_weld[i] = 0;
    }
    num_object = 0;
    num_object_weight_hack = 0;
}

//------------------------------------------------------------------------------------
void object_add_weight_hack(float x, float y, float z, float radius)
{
    // <ZZ> Hack for making the weights of certain vertices greater than normal,
    //      to prevent mesh inversion.
    if(num_object_weight_hack < MAX_WEIGHT_HACK)
    {
        object_weight_hack_xyz[num_object_weight_hack][X] = x;
        object_weight_hack_xyz[num_object_weight_hack][Y] = y;
        object_weight_hack_xyz[num_object_weight_hack][Z] = z;
        object_weight_hack_radius[num_object_weight_hack] = radius;
        num_object_weight_hack++;
    }
}

//-----------------------------------------------------------------------------------------------
void object_draw_weight_hack()
{
    // <ZZ> Draws all of the weight hack spheres...
    unsigned short i;
    repeat(i, num_object_weight_hack)
    {
        display_texture_off();
        display_blend_off();
        display_shade_off();
        display_sphere(white, object_weight_hack_xyz[i][X], object_weight_hack_xyz[i][Y], object_weight_hack_xyz[i][Z], object_weight_hack_radius[i], 8, 30);
    }
}

//-----------------------------------------------------------------------------------------------
void object_start()
{
    // <ZZ> This function starts building a new object...
    if(num_object < MAX_OBJECT)
    {
        // Clear out all of the object's data...
        object_texture[num_object] = 0;
        object_num_vertex[num_object] = 0;
        object_num_triangle[num_object] = 0;
        object_num_spring[num_object] = 0;
        object_num_weld[num_object] = 0;

        object_dampen[num_object] = 0.10f;
        object_toothpick_force[num_object] = 1.00f;
        object_spring_force[num_object] = 0.20f;
        object_return_force[num_object] = 0.03f;
        object_weight[num_object] = 1.0f;

        log_message("Starting object %d", num_object);
    }
    else
    {
        log_message("ERROR:  Ran out of objects");
    }

}

//------------------------------------------------------------------------------------
void object_add_vertex(float x, float y, float z)
{
    // This function adds a vertex at the specified location (and sets it's default
    // velocity and normal vectors)
    x*=global_object_scaling;
    y*=global_object_scaling;
    z*=global_object_scaling;
    if(num_object < MAX_OBJECT)
    {
        if(object_num_vertex[num_object] < MAX_VERTEX)
        {
            object_vertex_data[num_object][object_num_vertex[num_object]][X] = x;
            object_vertex_data[num_object][object_num_vertex[num_object]][Y] = y;
            object_vertex_data[num_object][object_num_vertex[num_object]][Z] = z;
            object_vertex_data[num_object][object_num_vertex[num_object]][VX] = 0.0f;
            object_vertex_data[num_object][object_num_vertex[num_object]][VY] = 0.0f;
            object_vertex_data[num_object][object_num_vertex[num_object]][VZ] = 0.0f;
            object_vertex_data[num_object][object_num_vertex[num_object]][NX] = 0.0f;
            object_vertex_data[num_object][object_num_vertex[num_object]][NY] = 0.0f;
            object_vertex_data[num_object][object_num_vertex[num_object]][NZ] = 1.0f;
            object_vertex_data[num_object][object_num_vertex[num_object]][OX] = x;
            object_vertex_data[num_object][object_num_vertex[num_object]][OY] = y;
            object_vertex_data[num_object][object_num_vertex[num_object]][OZ] = z;
            object_vertex_data[num_object][object_num_vertex[num_object]][TX] = 0.0f;
            object_vertex_data[num_object][object_num_vertex[num_object]][TY] = 0.0f;
//log_message("Added vertex at %f, %f, %f", x, y, z);
            object_num_vertex[num_object]++;
        }
        else
        {
            error_vertex_count++;
            log_message("ERROR:  Ran out of vertices (count == %d)", error_vertex_count);
        }
    }
}

//------------------------------------------------------------------------------------
void object_add_triangle(unsigned short va, unsigned short vb, unsigned short vc)
{
    // This function puts a triangle between three vertices...
    if(num_object < MAX_OBJECT)
    {
        if(object_num_triangle[num_object] < MAX_TRIANGLE)
        {
            if(va < object_num_vertex[num_object] && vb < object_num_vertex[num_object] && vc < object_num_vertex[num_object])
            {
                object_triangle_list[num_object][object_num_triangle[num_object]][0] = va;
                object_triangle_list[num_object][object_num_triangle[num_object]][1] = vb;
                object_triangle_list[num_object][object_num_triangle[num_object]][2] = vc;
                object_num_triangle[num_object]++;
            }
        }
        else
        {
            error_triangle_count++;
            log_message("ERROR:  Ran out of triangles (count == %d)", error_triangle_count);
        }
    }
}

//------------------------------------------------------------------------------------
void object_weld_vertices(unsigned short a, unsigned short object_a, unsigned short b, unsigned short object_b)
{
    // <ZZ> This function welds two vertices together...  X format specifies new vertices
    //      if the texture coordinates are different, so seams appear...  This fixes that
    //      problem...
    unsigned short i;
    if(object_a < MAX_OBJECT && object_b < MAX_OBJECT)
    {
        if(object_num_weld[object_a] < MAX_WELD && a < object_num_vertex[object_a] && b < object_num_vertex[object_b])
        {
            log_message("  Welding together vertex %d and %d", a, b);

            // Handle welds on same object...
            if(object_a == object_b)
            {
                // Replace all instances of first vertex in springs with second...
                repeat(i, object_num_spring[object_a])
                {
                    if(object_spring_list[object_a][i][0] == a)
                    {
                        object_spring_list[object_a][i][0] = b;
                    }
                    if(object_spring_list[object_a][i][1] == a)
                    {
                        object_spring_list[object_a][i][1] = b;
                    }
                }
            }

            object_weld_list[object_a][object_num_weld[object_a]][0] = a;
            object_weld_list[object_a][object_num_weld[object_a]][1] = b;
            object_weld_list[object_a][object_num_weld[object_a]][2] = object_b;
            object_num_weld[object_a]++;
        }
    }
}

//------------------------------------------------------------------------------------
void object_weld_redundant_vertices(float tolerance, unsigned short object_a, unsigned short object_b)
{
    // <ZZ> This function goes through all vertices in the mesh, welding together any
    //      that are really, really close to one another.
    unsigned short i, j;
    float x, y, z, dis;


log_message("Welding all redundant vertices with a tolerance of %f -- objects %d and %d", tolerance, object_a, object_b);

    tolerance = tolerance*tolerance;
    if(object_a < MAX_OBJECT && object_b < MAX_OBJECT)
    {
        if(object_a == object_b)
        {
            repeat(i, object_num_vertex[object_a])
            {
                j = i+1;
                while(j < object_num_vertex[object_b])
                {
                    x = object_vertex_data[object_a][i][X] - object_vertex_data[object_b][j][X];
                    y = object_vertex_data[object_a][i][Y] - object_vertex_data[object_b][j][Y];
                    z = object_vertex_data[object_a][i][Z] - object_vertex_data[object_b][j][Z];
                    dis = x*x + y*y + z*z;
                    if(dis < tolerance)
                    {
                        object_weld_vertices(i, object_a, j, object_b);
                    }
                    j++;
                }
            }
        }
        else
        {
            repeat(i, object_num_vertex[object_a])
            {
                repeat(j, object_num_vertex[object_b])
                {
                    x = object_vertex_data[object_a][i][X] - object_vertex_data[object_b][j][X];
                    y = object_vertex_data[object_a][i][Y] - object_vertex_data[object_b][j][Y];
                    z = object_vertex_data[object_a][i][Z] - object_vertex_data[object_b][j][Z];
                    dis = x*x + y*y + z*z;
                    if(dis < tolerance)
                    {
                        object_weld_vertices(i, object_a, j, object_b);
                    }
                }
            }
        }
    }    
}


//------------------------------------------------------------------------------------
void object_set_normal(unsigned short vertex, float nx, float ny, float nz)
{
    // This function sets the normal vector for a given vertex, and makes sure it's
    // of unit length...
    float length;
    if(num_object < MAX_OBJECT)
    {
        if(vertex < object_num_vertex[num_object])
        {
            // Normalize the vector...
            length = nx*nx + ny*ny + nz*nz;
            length = (float) sqrt(length);
            if(length > 0.0f)
            {
                nx = nx/length;
                ny = ny/length;
                nz = nz/length;


                // Put the vector into our data block...
                object_vertex_data[num_object][vertex][NX] = nx;
                object_vertex_data[num_object][vertex][NY] = ny;
                object_vertex_data[num_object][vertex][NZ] = nz;
            }
        }
    }
}

//------------------------------------------------------------------------------------
void object_set_texpos(unsigned short vertex, float tx, float ty)
{
    // This function sets the texture position of a given vertex
    if(num_object < MAX_OBJECT)
    {
        if(vertex < object_num_vertex[num_object])
        {
            object_vertex_data[num_object][vertex][TX] = tx;
            object_vertex_data[num_object][vertex][TY] = ty;
        }
    }
}

//------------------------------------------------------------------------------------
void object_set_texture(unsigned int texture)
{
    // <ZZ> This function sets the texture for the current object...
    if(num_object < MAX_OBJECT)
    {
        object_texture[num_object] = texture;
    }
}

//-----------------------------------------------------------------------------------------------
void object_add_spring(unsigned short va, unsigned short vb)
{
    // <ZZ> This function adds a spring to the current object, between the two specified
    //      vertices...  Makes sure that spring is not a duplicate...
    unsigned short i;
    float x, y, z;

    if(num_object < MAX_OBJECT)
    {
        if(object_num_spring[num_object] < MAX_SPRING)
        {
            if(va < object_num_vertex[num_object] && vb < object_num_vertex[num_object])
            {
                // Check to make sure spring doesn't already exist...
                repeat(i, object_num_spring[num_object])
                {
                    if((object_spring_list[num_object][i][0] == va && object_spring_list[num_object][i][1] == vb) || (object_spring_list[num_object][i][0] == vb && object_spring_list[num_object][i][1] == va))
                    {
                        // Spring already exists...
                        return;
                    }
                }


                // Add the new spring...
                object_spring_list[num_object][object_num_spring[num_object]][0] = va;
                object_spring_list[num_object][object_num_spring[num_object]][1] = vb;
                x = object_vertex_data[num_object][va][X] - object_vertex_data[num_object][vb][X];
                y = object_vertex_data[num_object][va][Y] - object_vertex_data[num_object][vb][Y];
                z = object_vertex_data[num_object][va][Z] - object_vertex_data[num_object][vb][Z];
                object_spring_length[num_object][object_num_spring[num_object]] = (float) sqrt(x*x + y*y + z*z);
                object_num_spring[num_object]++;
            }
        }
        else
        {
            error_spring_count++;
            log_message("ERROR:  Ran out of springs (count == %d)", error_spring_count);
        }
    }
}

//-----------------------------------------------------------------------------------------------
void object_auto_spring()
{
    // <ZZ> This function automatically adds springs to the current object...
    unsigned short i, a, b, c;
    if(num_object < MAX_OBJECT)
    {
        log_message("Automatically adding springs to the model...");


        // Add a springs for each triangle...
        repeat(i, object_num_triangle[num_object])
        {
            a = object_triangle_list[num_object][i][0];
            b = object_triangle_list[num_object][i][1];
            c = object_triangle_list[num_object][i][2];


            // Add springs along connections of triangles...
            object_add_spring(a, b);
            object_add_spring(b, c);
            object_add_spring(c, a);
        }
    }
}

//------------------------------------------------------------------------------------
void object_determine_bounds(unsigned short object)
{
    // This function determines the center of the object, and the radius of
    // a bounding sphere that encompasses all of the object's vertices...
    float x, y, z, dis, max_dis;
    float min_xyz[3];
    float max_xyz[3];
    int i;

    if(object < MAX_OBJECT)
    {
        object_center_xyz[object][X] = 0.0f;
        object_center_xyz[object][Y] = 0.0f;
        object_center_xyz[object][Z] = 0.0f;
        object_bounding_radius[object] = 0.0f;
        if(object_num_vertex[object] > 0)
        {
            // Find the extents of the object...
            min_xyz[X] = object_vertex_data[object][0][X];
            min_xyz[Y] = object_vertex_data[object][0][Y];
            min_xyz[Z] = object_vertex_data[object][0][Z];
            max_xyz[X] = object_vertex_data[object][0][X];
            max_xyz[Y] = object_vertex_data[object][0][Y];
            max_xyz[Z] = object_vertex_data[object][0][Z];
            repeat(i, object_num_vertex[object])
            {
                min_xyz[X] = (object_vertex_data[object][i][X] < min_xyz[X]) ? object_vertex_data[object][i][X] : min_xyz[X];
                min_xyz[Y] = (object_vertex_data[object][i][Y] < min_xyz[Y]) ? object_vertex_data[object][i][Y] : min_xyz[Y];
                min_xyz[Z] = (object_vertex_data[object][i][Z] < min_xyz[Z]) ? object_vertex_data[object][i][Z] : min_xyz[Z];
                max_xyz[X] = (object_vertex_data[object][i][X] > max_xyz[X]) ? object_vertex_data[object][i][X] : max_xyz[X];
                max_xyz[Y] = (object_vertex_data[object][i][Y] > max_xyz[Y]) ? object_vertex_data[object][i][Y] : max_xyz[Y];
                max_xyz[Z] = (object_vertex_data[object][i][Z] > max_xyz[Z]) ? object_vertex_data[object][i][Z] : max_xyz[Z];
            }
            object_center_xyz[object][X] = (min_xyz[X] + max_xyz[X])*0.5f;
            object_center_xyz[object][Y] = (min_xyz[Y] + max_xyz[Y])*0.5f;
            object_center_xyz[object][Z] = (min_xyz[Z] + max_xyz[Z])*0.5f;


            // Find the greatest distance from that center to any
            // of the object's vertices...
            max_dis = 0.0f;
            repeat(i, object_num_vertex[object])
            {
                x = (object_vertex_data[object][i][X] - object_center_xyz[object][X]);
                y = (object_vertex_data[object][i][Y] - object_center_xyz[object][Y]);
                z = (object_vertex_data[object][i][Z] - object_center_xyz[object][Z]);
                dis = x*x + y*y + z*z;
                max_dis = (dis > max_dis) ? dis : max_dis;
            }
            object_bounding_radius[object] = (float) sqrt(max_dis);
        }
    }
}

//-----------------------------------------------------------------------------------------------
void object_calculate_normals()
{
    // <ZZ> This function determines the normal vector for each vertex, based on the
    //      triangles that connect to it...
    unsigned short object, i, a, b, c;
    float length, side_xyz[3], other_xyz[3], x, y, z;


    repeat(object, num_object)
    {
        // Clear out all the vertex normals
        repeat(i, object_num_vertex[object])
        {
            object_vertex_data[object][i][NX] = 0.0f;
            object_vertex_data[object][i][NY] = 0.0f;
            object_vertex_data[object][i][NZ] = 0.0f;
        }


        // Calculate all the triangle normal vectors...
        repeat(i, object_num_triangle[object])
        {
            // Find the 3 vertices of this triangle...
            a = object_triangle_list[object][i][0];
            b = object_triangle_list[object][i][1];
            c = object_triangle_list[object][i][2];

            // Find the first edge of this triangle
            side_xyz[X] = object_vertex_data[object][c][X] - object_vertex_data[object][b][X];
            side_xyz[Y] = object_vertex_data[object][c][Y] - object_vertex_data[object][b][Y];
            side_xyz[Z] = object_vertex_data[object][c][Z] - object_vertex_data[object][b][Z];

            // Find another edge of this triangle
            other_xyz[X] = object_vertex_data[object][a][X] - object_vertex_data[object][b][X];
            other_xyz[Y] = object_vertex_data[object][a][Y] - object_vertex_data[object][b][Y];
            other_xyz[Z] = object_vertex_data[object][a][Z] - object_vertex_data[object][b][Z];

            // Determine the normal for this triangle by crossing the two edges
            cross_product(side_xyz, other_xyz, object_triangle_normal[object][i]);
            x = object_triangle_normal[object][i][X];
            y = object_triangle_normal[object][i][Y];
            z = object_triangle_normal[object][i][Z];

            // Normalize the resulting vector...
            length = (float) sqrt(x*x + y*y + z*z);
            if(length > 0.0f)
            {
                x = x/length;
                y = y/length;
                z = z/length;
                object_triangle_normal[object][i][X] = x;
                object_triangle_normal[object][i][Y] = y;
                object_triangle_normal[object][i][Z] = z;
            }


            // Accumulate the triangle normals into the normal vectors of each attached vertex
            object_vertex_data[object][a][NX] += x;
            object_vertex_data[object][a][NY] += y;
            object_vertex_data[object][a][NZ] += z;

            object_vertex_data[object][b][NX] += x;
            object_vertex_data[object][b][NY] += y;
            object_vertex_data[object][b][NZ] += z;

            object_vertex_data[object][c][NX] += x;
            object_vertex_data[object][c][NY] += y;
            object_vertex_data[object][c][NZ] += z;
        }


        // Now renormalize the normals for each vertex...
        repeat(i, object_num_vertex[object])
        {
            x = object_vertex_data[object][i][NX];
            y = object_vertex_data[object][i][NY];
            z = object_vertex_data[object][i][NZ];
            length = (float) sqrt(x*x + y*y + z*z);
            if(length > 0.0f)
            {
                x = x/length;
                y = y/length;
                z = z/length;


                // Put the vector into our data block...
                object_vertex_data[object][i][NX] = x;
                object_vertex_data[object][i][NY] = y;
                object_vertex_data[object][i][NZ] = z;
            }
        }
    }    
}

//------------------------------------------------------------------------------------
void object_calculate_press(void)
{
    // <ZZ> This function determines the maximum amount any vertex may be
    //      moved -- vertices become more and more massive as they are displaced
    //      from their starting point.  This also helps us to prevent the mesh
    //      from colliding with itself...
    unsigned short object, i, j, k, a, b, c;
    float best, dis;
    float to_xyz[3];

    // For each object in the mesh
    repeat(object, num_object)
    {
        // For each vertex of the object
        repeat(i, object_num_vertex[object])
        {
            // Cast a ray in the opposite direction of its normal...
            to_xyz[X] = object_vertex_data[object][i][X] - MAX_PRESS*object_vertex_data[object][i][NX];
            to_xyz[Y] = object_vertex_data[object][i][Y] - MAX_PRESS*object_vertex_data[object][i][NY];
            to_xyz[Z] = object_vertex_data[object][i][Z] - MAX_PRESS*object_vertex_data[object][i][NZ];


            // Collide that ray with every triangle of the object...
            best = MAX_PRESS*0.25f;
            repeat(j, num_object)
            {
                repeat(k, object_num_triangle[j])
                {
                    a = object_triangle_list[j][k][0];
                    b = object_triangle_list[j][k][1];
                    c = object_triangle_list[j][k][2];
                    dis = collide_line_triangle(object_vertex_data[object][i], to_xyz, object_vertex_data[j][a], object_vertex_data[j][b], object_vertex_data[j][c], object_triangle_normal[j][k]);
                    if(dis > 0.01f && dis < best)
                    {
                        best = dis;
                    }
                }
            }
            object_vertex_data[object][i][PRESS] = (best*best);
log_message("Press for vertex %d-%d == %f", object, i, object_vertex_data[object][i][PRESS]);
            object_vertex_data[object][i][MULTIPLIER] = 0.0f;
        }
    }
}


//-----------------------------------------------------------------------------------------------
void object_end()
{
    // <ZZ> This function finishes building a new object...
    if(num_object < MAX_OBJECT)
    {
        if(object_num_vertex[num_object] > 0)
        {
            object_auto_spring();
            object_weld_redundant_vertices(WELD_TOLERANCE, num_object, num_object);
            log_message("Finished building object %d", num_object);
            num_object++;
        }
    }
}

//------------------------------------------------------------------------------------
void object_draw(unsigned short object, unsigned short pass)
{
    // <ZZ> This function draws the specified object...
    int i;
    unsigned short a, b, c;
    float vertex_xyz[3];
    float d;
    float o_xyz[3];


    if(object < num_object)
    {
        display_zbuffer_on();

        //display_texture_off();
        //display_blend_off();
        //display_shade_off();
        //display_sphere(white, object_center_xyz[object][X], object_center_xyz[object][Y], object_center_xyz[object][Z], object_bounding_radius[object], 8, 30);

        if(object_draw_wireframe)
        {
            if(!object_draw_spring)
            {
                // Draw a wireframe of each triangle...
                display_texture_off();
                display_blend_off();
                display_shade_off();
                display_color(white);
                repeat(i, object_num_triangle[object])
                {
                    a = object_triangle_list[object][i][0];
                    b = object_triangle_list[object][i][1];
                    c = object_triangle_list[object][i][2];
                    display_start_line();
                        display_vertex(object_vertex_data[object][a]);
                        display_vertex(object_vertex_data[object][b]);

                        display_vertex(object_vertex_data[object][b]);
                        display_vertex(object_vertex_data[object][c]);

                        display_vertex(object_vertex_data[object][c]);
                        display_vertex(object_vertex_data[object][a]);
                    display_end();
                }
            }
        }
        else
        {
            if(pass == 0)
            {
                // First pass is for front faces
                if(global_scope_cam)
                {
                    display_cull_off();
                }
                else
                {
                    display_cull_on();
                }


                // Calculate vertex lighting...
                repeat(i, object_num_vertex[object])
                {
                    o_xyz[X] = object_vertex_data[object][i][X] - scope_xyz[X];
                    o_xyz[Y] = object_vertex_data[object][i][Y] - scope_xyz[Y];
                    o_xyz[Z] = object_vertex_data[object][i][Z] - scope_xyz[Z];
                    normalize(o_xyz);
                    object_vertex_data[object][i][LIGHTX] = 0.5f;
                    object_vertex_data[object][i][LIGHTY] = dot_product(o_xyz, scope_fore_xyz);
                    d = (global_normal_length * 0.005f);
                    d = (d < 0.00f) ? 0.00f : d;
                    d = (d > 1.00f) ? 1.00f : d;
                    d = d*d;
                    d = 1.0f - d;
                    d*=global_cam_light;
                    d = (object_vertex_data[object][i][LIGHTY] * 0.25f) + (d*0.49f) + 0.25f;
                    object_vertex_data[object][i][LIGHTY] = d*(1.0f-global_ambient_light) + (global_ambient_light*0.99f);
                }
            }
            else
            {
                // Second pass is for back faces (drawn darker)
                display_cull_reversed();


                // Calculate vertex lighting...
                repeat(i, object_num_vertex[object])
                {
                    object_vertex_data[object][i][LIGHTX] = (camera_side_xyz[X]*object_vertex_data[object][i][NX] + camera_side_xyz[Y]*object_vertex_data[object][i][NY] + camera_side_xyz[Z]*object_vertex_data[object][i][NZ])*0.5f + 0.5f;
                    object_vertex_data[object][i][LIGHTY] = (camera_up_xyz[X]*object_vertex_data[object][i][NX] + camera_up_xyz[Y]*object_vertex_data[object][i][NY] + camera_up_xyz[Z]*object_vertex_data[object][i][NZ])*0.5f + 0.5f;
                }
            }
            if(display_multitexture)
            {
                // Draw each triangle of the model, with multiple textures...
                display_shade_off();
                display_blend_off();



                // Setup multitexturing
                display_multi_zero();
                display_texture_on();
                display_pick_texture(object_texture[object]);



                display_multi_one();
                display_texture_on();
                if(pass == 0)
                {
                    display_pick_texture(camlight_texture);
                    display_color(white);
                }
                else
                {
                    display_zbuffer_write_off();
                    display_pick_texture(light_texture);
                    display_color_alpha(dark);
                    display_blend_trans();
                }
                glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
                glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_ADD_SIGNED);
                glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB );
                glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
                glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE );
                glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );



                if(pass == 0)
                {
                    display_multi_two();
                    display_texture_on();
                    display_pick_texture(camlight_texture);
                    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
                    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_ADD_SIGNED);
                    glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB );
                    glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
                    glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE );
                    glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );
                }




                repeat(i, object_num_triangle[object])
                {
                    a = object_triangle_list[object][i][0];
                    b = object_triangle_list[object][i][1];
                    c = object_triangle_list[object][i][2];
                    display_start_fan();
                        display_multi_zero_xy(object_vertex_data[object][a][TX], object_vertex_data[object][a][TY]);
                        display_multi_one_xy(object_vertex_data[object][a][LIGHTX], object_vertex_data[object][a][LIGHTY]);
                        display_multi_two_xy(object_vertex_data[object][a][LIGHTX], object_vertex_data[object][a][LIGHTY]);
                        display_vertex(object_vertex_data[object][a]);


                        display_multi_zero_xy(object_vertex_data[object][b][TX], object_vertex_data[object][b][TY]);
                        display_multi_one_xy(object_vertex_data[object][b][LIGHTX], object_vertex_data[object][b][LIGHTY]);
                        display_multi_two_xy(object_vertex_data[object][b][LIGHTX], object_vertex_data[object][b][LIGHTY]);
                        display_vertex(object_vertex_data[object][b]);


                        display_multi_zero_xy(object_vertex_data[object][c][TX], object_vertex_data[object][c][TY]);
                        display_multi_one_xy(object_vertex_data[object][c][LIGHTX], object_vertex_data[object][c][LIGHTY]);
                        display_multi_two_xy(object_vertex_data[object][c][LIGHTX], object_vertex_data[object][c][LIGHTY]);
                        display_vertex(object_vertex_data[object][c]);
                    display_end();
                }


                // Turn off multitexturing...
                display_multi_two();
                display_texture_off();
                display_multi_one();
                display_texture_off();
                display_multi_zero();
            }
            else
            {
                // Draw each triangle of the model, with a single texture...
                display_texture_on();
                display_blend_off();
                display_shade_off();
                display_pick_texture(object_texture[object]);
                repeat(i, object_num_triangle[object])
                {
                    a = object_triangle_list[object][i][0];
                    b = object_triangle_list[object][i][1];
                    c = object_triangle_list[object][i][2];
                    display_start_fan();
                        display_texpos(&object_vertex_data[object][a][TX]);  display_vertex(object_vertex_data[object][a]);
                        display_texpos(&object_vertex_data[object][b][TX]);  display_vertex(object_vertex_data[object][b]);
                        display_texpos(&object_vertex_data[object][c][TX]);  display_vertex(object_vertex_data[object][c]);
                    display_end();
                }
            }
            display_zbuffer_write_on();
        }





        // Draw each hit triangle...
        if(object_draw_hit)
        {
            display_cull_off();
            display_texture_off();
            display_blend_off();
            display_shade_off();
            repeat(i, object_num_triangle[object])
            {
                if(object_triangle_hit[object][i] < MAX_SCOPE_CONTROL)
                {
                    display_color_alpha(green);
                    a = object_triangle_list[object][i][0];
                    b = object_triangle_list[object][i][1];
                    c = object_triangle_list[object][i][2];
                    display_start_fan();
                        display_vertex(object_vertex_data[object][a]);
                        display_vertex(object_vertex_data[object][b]);
                        display_vertex(object_vertex_data[object][c]);
                    display_end();
                }
            }
            display_color_alpha(white);
        }




        if(object_draw_normal)
        {
            // Draw the normals for each vertex
            display_texture_off();
            display_blend_off();
            display_shade_off();
            display_color(green);
            repeat(i, object_num_vertex[object])
            {
                vertex_xyz[X] = object_vertex_data[object][i][X] + object_vertex_data[object][i][NX] * object_normal_scale;
                vertex_xyz[Y] = object_vertex_data[object][i][Y] + object_vertex_data[object][i][NY] * object_normal_scale;
                vertex_xyz[Z] = object_vertex_data[object][i][Z] + object_vertex_data[object][i][NZ] * object_normal_scale;
                display_start_line();
                    display_vertex(object_vertex_data[object][i]);
                    display_vertex(vertex_xyz);
                display_end();
            }
        }


        if(object_draw_spring)
        {
            // Draw every spring in the object
            display_texture_off();
            display_blend_off();
            display_shade_off();
            display_color(red);
            repeat(i, object_num_spring[object])
            {
                a = object_spring_list[object][i][0];
                b = object_spring_list[object][i][1];
                display_start_line();
                    display_vertex(object_vertex_data[object][a]);
                    display_vertex(object_vertex_data[object][b]);
                display_end();
            }
        }
    }
}

//-----------------------------------------------------------------------------------------------
void object_keep_welded_vertices_together()
{
    // This function keeps the welded vertices together, copying position and normal
    // information...
    unsigned short object, i, a, b, object_b;
    float x, y, z;
//    float ox, oy, oz;

    repeat(object, num_object)
    {
        // Keep welded vertices together...
        repeat(i, object_num_weld[object])
        {
            a = object_weld_list[object][i][0];
            b = object_weld_list[object][i][1];
            object_b = object_weld_list[object][i][2];
            if(object_vertex_data[object][a][WELDSTOP] > 0.5f && object_vertex_data[object_b][b][WELDSTOP] < 0.5f)
            {
                x = object_vertex_data[object][a][X];
                y = object_vertex_data[object][a][Y];
                z = object_vertex_data[object][a][Z];
            }
            else if(object_vertex_data[object][a][WELDSTOP] < 0.5f && object_vertex_data[object_b][b][WELDSTOP] > 0.5f)
            {
                x = object_vertex_data[object_b][b][X];
                y = object_vertex_data[object_b][b][Y];
                z = object_vertex_data[object_b][b][Z];
            }
            else
            {
                x = (object_vertex_data[object][a][X] + object_vertex_data[object_b][b][X])*0.5f;
                y = (object_vertex_data[object][a][Y] + object_vertex_data[object_b][b][Y])*0.5f;
                z = (object_vertex_data[object][a][Z] + object_vertex_data[object_b][b][Z])*0.5f;
            }
            object_vertex_data[object][a][X] = x;
            object_vertex_data[object][a][Y] = y;
            object_vertex_data[object][a][Z] = z;
            object_vertex_data[object_b][b][X] = x;
            object_vertex_data[object_b][b][Y] = y;
            object_vertex_data[object_b][b][Z] = z;
            object_vertex_data[object][a][NX] = object_vertex_data[object_b][b][NX];
            object_vertex_data[object][a][NY] = object_vertex_data[object_b][b][NY];
            object_vertex_data[object][a][NZ] = object_vertex_data[object_b][b][NZ];
        }
    }
}

//-----------------------------------------------------------------------------------------------
void object_update()
{
    // This function updates all of the vertex/spring physics for the objects...
    // Collisions are handled elsewhere...
    unsigned short object, i, j, a, b, c;
    float length, x, y, z, force, motion, best_dis, dis, min_dis, inverse;
    float spring_force, toothpick_force;
    float radius;

    repeat(object, num_object)
    {
        // Determine the forces for this object...
        spring_force = 0.5f*global_spring_force*object_spring_force[object];
        toothpick_force = 0.5f*global_toothpick_force*object_toothpick_force[object];


        // Update all of the positions based on velocity...
        repeat(i, object_num_vertex[object])
        {
            object_vertex_data[object][i][X] += object_vertex_data[object][i][VX];
            object_vertex_data[object][i][Y] += object_vertex_data[object][i][VY];
            object_vertex_data[object][i][Z] += object_vertex_data[object][i][VZ];
        }


        // Dampen all of the velocities...
        force = 1.0f - (object_dampen[object]*global_dampen);
        repeat(i, object_num_vertex[object])
        {
            object_vertex_data[object][i][VX] *= force;
            object_vertex_data[object][i][VY] *= force;
            object_vertex_data[object][i][VZ] *= force;
        }


        // Update all of the velocities based on spring effects...
        repeat(i, object_num_spring[object])
        {
            a = object_spring_list[object][i][0];
            b = object_spring_list[object][i][1];
            x = object_vertex_data[object][a][X] - object_vertex_data[object][b][X];
            y = object_vertex_data[object][a][Y] - object_vertex_data[object][b][Y];
            z = object_vertex_data[object][a][Z] - object_vertex_data[object][b][Z];
            length = (float) sqrt(x*x + y*y + z*z);
            force = object_spring_length[object][i] - length;
            length+=0.0001f;
            force /= length;
            motion = force*toothpick_force;
            force = force*spring_force;


            // Spring force is used to modify velocity (loose springs)
            object_vertex_data[object][a][VX] += x*force;
            object_vertex_data[object][a][VY] += y*force;
            object_vertex_data[object][a][VZ] += z*force;
            object_vertex_data[object][b][VX] -= x*force;
            object_vertex_data[object][b][VY] -= y*force;
            object_vertex_data[object][b][VZ] -= z*force;


            // Toothpick force is used to force distance (really stiff springs)
            object_vertex_data[object][a][X] += x*motion;
            object_vertex_data[object][a][Y] += y*motion;
            object_vertex_data[object][a][Z] += z*motion;
            object_vertex_data[object][b][X] -= x*motion;
            object_vertex_data[object][b][Y] -= y*motion;
            object_vertex_data[object][b][Z] -= z*motion;
        }




        // Update all of the velocities based on spring to original position effects...
        force = (global_return_force*object_return_force[object]);
        inverse = 1.0f - force;
        repeat(i, object_num_vertex[object])
        {
            object_vertex_data[object][i][X] = object_vertex_data[object][i][X]*inverse + object_vertex_data[object][i][OX]*force;
            object_vertex_data[object][i][Y] = object_vertex_data[object][i][Y]*inverse + object_vertex_data[object][i][OY]*force;
            object_vertex_data[object][i][Z] = object_vertex_data[object][i][Z]*inverse + object_vertex_data[object][i][OZ]*force;
            x = object_vertex_data[object][i][X] - object_vertex_data[object][i][OX];
            y = object_vertex_data[object][i][Y] - object_vertex_data[object][i][OY];
            z = object_vertex_data[object][i][Z] - object_vertex_data[object][i][OZ];
            dis = x*x + y*y + z*z;
            object_vertex_data[object][i][MULTIPLIER] = 1.0f - (dis / object_vertex_data[object][i][PRESS]);
            object_vertex_data[object][i][MULTIPLIER] = (object_vertex_data[object][i][MULTIPLIER] < 0.0f) ? 0.0f : object_vertex_data[object][i][MULTIPLIER];
            object_vertex_data[object][i][WELDSTOP] = 0.0f;
        }


        // Apply the weight hack to vertices within the hack's radius...
        repeat(j, num_object_weight_hack)
        {
            radius = object_weight_hack_radius[j];
            radius*=radius;
            repeat(i, object_num_vertex[object])
            {
                x = object_vertex_data[object][i][X] - object_weight_hack_xyz[j][X];
                y = object_vertex_data[object][i][Y] - object_weight_hack_xyz[j][Y];
                z = object_vertex_data[object][i][Z] - object_weight_hack_xyz[j][Z];
                dis = x*x + y*y + z*z;
                if(dis < radius)
                {
                    object_vertex_data[object][i][X] = object_vertex_data[object][i][OX];
                    object_vertex_data[object][i][Y] = object_vertex_data[object][i][OY];
                    object_vertex_data[object][i][Z] = object_vertex_data[object][i][OZ];
                    object_vertex_data[object][i][MULTIPLIER] = 0.0f;
                }
            }
        }


        // Determine main object center and radius...
        object_determine_bounds(object);


        // Calculate triangle centers and radii
        min_dis = global_scope_radius * global_scope_radius;
        repeat(i, object_num_triangle[object])
        {
            a = object_triangle_list[object][i][0];
            b = object_triangle_list[object][i][1];
            c = object_triangle_list[object][i][2];
            object_triangle_center_xyz[object][i][X] = (object_vertex_data[object][a][X] + object_vertex_data[object][b][X] + object_vertex_data[object][c][X])*0.333333333333333333333f;
            object_triangle_center_xyz[object][i][Y] = (object_vertex_data[object][a][Y] + object_vertex_data[object][b][Y] + object_vertex_data[object][c][Y])*0.333333333333333333333f;
            object_triangle_center_xyz[object][i][Z] = (object_vertex_data[object][a][Z] + object_vertex_data[object][b][Z] + object_vertex_data[object][c][Z])*0.333333333333333333333f;

            x = (object_vertex_data[object][a][X] - object_triangle_center_xyz[object][i][X]);
            y = (object_vertex_data[object][a][Y] - object_triangle_center_xyz[object][i][Y]);
            z = (object_vertex_data[object][a][Z] - object_triangle_center_xyz[object][i][Z]);
            best_dis = x*x + y*y + z*z;

            x = (object_vertex_data[object][b][X] - object_triangle_center_xyz[object][i][X]);
            y = (object_vertex_data[object][b][Y] - object_triangle_center_xyz[object][i][Y]);
            z = (object_vertex_data[object][b][Z] - object_triangle_center_xyz[object][i][Z]);
            dis = x*x + y*y + z*z;
            best_dis = (dis > best_dis) ? dis : best_dis;

            x = (object_vertex_data[object][c][X] - object_triangle_center_xyz[object][i][X]);
            y = (object_vertex_data[object][c][Y] - object_triangle_center_xyz[object][i][Y]);
            z = (object_vertex_data[object][c][Z] - object_triangle_center_xyz[object][i][Z]);
            dis = x*x + y*y + z*z;
            best_dis = (dis > best_dis) ? dis : best_dis;

            best_dis = (best_dis > min_dis) ? best_dis : min_dis;
            best_dis = (float) sqrt(best_dis);
            best_dis += (global_scope_segment_length*0.5f);
            best_dis*=best_dis;

            object_triangle_bounding_radius[object][i] = (float) sqrt(best_dis);
        } 
    }
}

//------------------------------------------------------------------------------------
void object_fix_z(void)
{
    // <ZZ> This function moves all the objects around such that the lowest z vertex is at
    //      0.0f...
    unsigned short object, i;
    float minz;

    minz = 999999999.0f;
    repeat(object, num_object)
    {
        repeat(i, object_num_vertex[object])
        {
            if(object_vertex_data[object][i][Z] < minz)
            {
                minz = object_vertex_data[object][i][Z];
            }
        }
    }
    repeat(object, num_object)
    {
        repeat(i, object_num_vertex[object])
        {
            object_vertex_data[object][i][Z]-=minz;
            object_vertex_data[object][i][OZ]-=minz;
        }
    }
}

//------------------------------------------------------------------------------------
void object_export(unsigned char* filename)
{
    // <ZZ> This function saves the current mesh object as a .X file...
    unsigned char fullname[1024];
    unsigned short i, j;
    FILE* output_file;


    strupr(filename);
    sprintf(fullname, "MODELS\\%s", filename);
    log_message("Attempting to write current object as %s", fullname);
    if(strcmp(filename, "TEST.X") == 0)
    {
        log_message("ERROR:  %s is a reserved filename", filename);
        return;
    }
    output_file = fopen(fullname, "w");
    if(output_file)
    {
        fprintf(output_file, "xof 0303txt 0032\n");
        fprintf(output_file, "# Export file from endoscopy simulation\n");


        fprintf(output_file, "Frame Wrapper\n");
        fprintf(output_file, "{\n");
        fprintf(output_file, "    FrameTransformMatrix\n");
        fprintf(output_file, "    {\n");
        fprintf(output_file, "        1.0, 0.0, 0.0, 0.0,\n");
        fprintf(output_file, "        0.0, 1.0, 0.0, 0.0,\n");
        fprintf(output_file, "        0.0, 0.0, 1.0, 0.0,\n");
        fprintf(output_file, "        0.0, 0.0, 0.0, 1.0;;\n");
        fprintf(output_file, "    }\n");
        repeat(i, num_object)
        {
            fprintf(output_file, "    Mesh Object%d\n", i);
            fprintf(output_file, "    {\n");
            fprintf(output_file, "        %d;\n", object_num_vertex[i]);
            repeat(j, object_num_vertex[i])
            {
                fprintf(output_file, "        %f; %f; %f;", object_vertex_data[i][j][X], object_vertex_data[i][j][Y], object_vertex_data[i][j][Z]);
                if((j+1) < object_num_vertex[i])
                {
                    fprintf(output_file, ",\n");
                }
                else
                {
                    fprintf(output_file, ";\n");
                }
            }
            fprintf(output_file, "        %d;\n", object_num_triangle[i]);
            repeat(j, object_num_triangle[i])
            {
                fprintf(output_file, "        3;%d, %d, %d;", object_triangle_list[i][j][0], object_triangle_list[i][j][1], object_triangle_list[i][j][2]);
                if((j+1) < object_num_triangle[i])
                {
                    fprintf(output_file, ",\n");
                }
                else
                {
                    fprintf(output_file, ";\n");
                }
            }


            fprintf(output_file, "        MeshNormals\n");
            fprintf(output_file, "        {\n");
            fprintf(output_file, "            %d;\n", object_num_vertex[i]);
            repeat(j, object_num_vertex[i])
            {
                fprintf(output_file, "            %f; %f; %f;", object_vertex_data[i][j][NX], object_vertex_data[i][j][NY], object_vertex_data[i][j][NZ]);
                if((j+1) < object_num_vertex[i])
                {
                    fprintf(output_file, ",\n");
                }
                else
                {
                    fprintf(output_file, ";\n");
                }
            }
            fprintf(output_file, "        }\n");


            fprintf(output_file, "        TextureFilename\n");
            fprintf(output_file, "        {\n");
            fprintf(output_file, "            \".\\\\COMMON\\\\DEFAULT.TGA\";\n");
            fprintf(output_file, "        }\n");



            fprintf(output_file, "        MeshTextureCoords\n");
            fprintf(output_file, "        {\n");
            fprintf(output_file, "            %d;\n", object_num_vertex[i]);
            repeat(j, object_num_vertex[i])
            {
                fprintf(output_file, "            %f; %f;", object_vertex_data[i][j][TX], object_vertex_data[i][j][TY]);
                if((j+1) < object_num_vertex[i])
                {
                    fprintf(output_file, ",\n");
                }
                else
                {
                    fprintf(output_file, ";\n");
                }
            }
            fprintf(output_file, "        }\n");


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


        fclose(output_file);
        log_message("  File written");
        return;
    }
    log_message("ERROR:  Unable to open datafile %s", fullname);
}

//------------------------------------------------------------------------------------
void object_auto_texpos()
{
    // <ZZ> This function automagically determines "okay" texture positions,
    //      since the generic airway model was untextured...
    unsigned short object, i;
    float x, y;

    // Go through each object...
    repeat(object, num_object)
    {
        // Go through each of its vertices...
        repeat(i, object_num_vertex[object])
        {
            x = object_vertex_data[object][i][X]*0.04f;
            y = object_vertex_data[object][i][Y]*0.04f;
            x -= object_vertex_data[object][i][Y]*0.04f;
            x -= object_vertex_data[object][i][Z]*0.05f;
            y += object_vertex_data[object][i][Z]*0.04f;

            x += object_vertex_data[object][i][NY]*0.4f;
            y += object_vertex_data[object][i][NX]*0.5f;

            object_vertex_data[object][i][TX] = x;
            object_vertex_data[object][i][TY] = y;
        }
    }
}

//------------------------------------------------------------------------------------
