/**********************************Module**********************************\
*
* geom.c
*
* 3D FlowerBox screen saver
* Geometry routines
*
* Copyright (c) 1995 Microsoft Corporation
*
\**************************************************************************/

#include "precomp.h"
#pragma hdrstop

// Sphere radius
#define RADIUS 1

// Values to map a 2D point onto a 3D plane
// Base point and axes to map X and Y coordinates onto
typedef struct _PLANE_MAP
{
    PT3 base, x_axis, y_axis;
} PLANE_MAP;

// Data area used by the current geometry
// Base points and generated points
PT3 pts[MAXPTS], npts[MAXPTS];
// Scaling factor for spherical projection
FLT vlen[MAXPTS];
// Normals
PT3 normals[MAXPTS];
// Vertex data indices
int index[MAXPTS*2];
// Triangle strip sizes
int strip_size[MAXSIDES*MAXSUBDIV];

void InitCube(GEOMETRY *geom);
void InitTetra(GEOMETRY *geom);
void InitPyramids(GEOMETRY *geom);
void InitCylinder(GEOMETRY *geom);
void InitSpring(GEOMETRY *geom);

GEOMETRY cube_geom = {InitCube};
GEOMETRY tetra_geom = {InitTetra};
GEOMETRY pyramids_geom = {InitPyramids};
GEOMETRY cylinder_geom = {InitCylinder};
GEOMETRY spring_geom = {InitSpring};

GEOMETRY *geom_table[] =
{
    &cube_geom,
    &tetra_geom,
    &pyramids_geom,
    &cylinder_geom,
    &spring_geom
};

extern BOOL bOgl11;
extern BOOL bCheckerOn;

/******************************Public*Routine******************************\
*
* InitVlen
*
* Precomputes scaling factor for spherical projection
*
\**************************************************************************/

void InitVlen(GEOMETRY *geom, int npts, PT3 *pts)
{
    FLT d;
    FLT *vl;

    vl = vlen;
    while (npts-- > 0)
    {
        d = V3Len(pts);

        // Don't allow really close points because this leads to
        // numeric instability and really large objects
        assert(d > 0.01f);

        // Geometries are created with size one, filling the area
        // from -.5 to .5.  This leads to distances generally less
        // than one, which leaves off half of the interesting morphing
        // effects due to the projection
        // Scaling up the scaling factor allows the values to
        // be both above and below one
        d *= geom->init_sf;
        
        assert(d > 0.0001f);
        
        *vl++ = (RADIUS-d)/d;
        
#if 0
        dprintf(("Distance is %f, vl %f\n", d, *(vl-1)));
#endif

        pts++;
    }
}

/******************************Public*Routine******************************\
*
* MapToSide
*
* Takes x,y coordinates in the range 0-1 and maps them onto the given
* side plane for the current geometry
*
\**************************************************************************/

void MapToSide(PLANE_MAP *map, FLT x, FLT y, PT3 *pt)
{
    pt->x = x*map->x_axis.x+y*map->y_axis.x+map->base.x;
    pt->y = x*map->x_axis.y+y*map->y_axis.y+map->base.y;
    pt->z = x*map->x_axis.z+y*map->y_axis.z+map->base.z;

}

void DrawWithVArrays (GEOMETRY *geom)
{
    int side, ss, idc, k;
    unsigned int *idx;

    for (side = 0; side < geom->nsides; side++) {
        geom->sides[side].dBuf = (GLuint *) LocalAlloc (LMEM_FIXED, 
                                                        sizeof (GLuint) * 
                                                        3 * MAXPTS * 2);
        k = 0;
        idx = geom->sides[side].strip_index;
        for (ss = 0; ss < geom->sides[side].nstrips; ss++) {
            if (geom->sides[side].strip_size[ss] < 3) continue;
            for (idc = 2; idc < geom->sides[side].strip_size[ss]; idc++) {
                if (!(idc % 2)) { //even
                    geom->sides[side].dBuf[k++] = *(idx+idc-2); 
                    geom->sides[side].dBuf[k++] = *(idx+idc-1); 
                } else {
                    geom->sides[side].dBuf[k++] = *(idx+idc-1); 
                    geom->sides[side].dBuf[k++] = *(idx+idc-2); 
                }
                geom->sides[side].dBuf[k++] = *(idx+idc); 
            }
            idx += geom->sides[side].strip_size[ss];
        }
        geom->sides[side].num_eles = k;
    }
    glNormalPointer (GL_FLOAT, sizeof (PT3), 
                     (GLfloat *)&(geom->normals[0].x));
    glVertexPointer (3, GL_FLOAT, sizeof (PT3), 
                     (GLfloat *)&(geom->npts[0].x));
    glEnableClientState (GL_VERTEX_ARRAY);
    glEnableClientState (GL_NORMAL_ARRAY);
    
    glDisableClientState (GL_COLOR_ARRAY);
    glDisableClientState (GL_INDEX_ARRAY);
    glDisableClientState (GL_EDGE_FLAG_ARRAY);
    glDisableClientState (GL_TEXTURE_COORD_ARRAY);
}
/******************************Public*Routine******************************\
*
* InitCube
*
* Initialize the cube's geometry
*
\**************************************************************************/

#define CUBE_SIDES 6

PLANE_MAP cube_planes[CUBE_SIDES] =
{
    -0.5f, -0.5f,  0.5f,  1.0f,  0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
     0.5f, -0.5f, -0.5f, -1.0f,  0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
     0.5f,  0.5f, -0.5f, -1.0f,  0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  1.0f,  0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  0.0f,  1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
    -0.5f,  0.5f, -0.5f,  0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f
};

#define CUBE_IDX(side, x, y) ((side)*side_pts+(x)*(config.subdiv+1)+(y))

void InitCube(GEOMETRY *geom)
{
    int side, x, y;
    PT3 *pt;
    unsigned int *sz, *idx;
    int side_pts;

    side_pts = (config.subdiv+1)*(config.subdiv+1);
    
    geom->nsides = CUBE_SIDES;
    geom->pts = &pts[0];
    geom->npts = &npts[0];
    geom->normals = &normals[0];

    geom->min_sf = -1.1f;
    geom->max_sf = 5.1f;
    geom->sf_inc = 0.05f;
    geom->init_sf = 2.0f;
    
    // Generate triangle strip data
    sz = &strip_size[0];
    idx = &index[0];
    for (side = 0; side < geom->nsides; side++)
    {
        geom->sides[side].nstrips = config.subdiv;
        geom->sides[side].strip_size = sz;
        geom->sides[side].strip_index = idx;
        
        for (x = 0; x < config.subdiv; x++)
        {
            *sz++ = (config.subdiv+1)*2;

            for (y = 0; y < config.subdiv+1; y++)
            {
                *idx++ = CUBE_IDX(side, x, y);
                *idx++ = CUBE_IDX(side, x+1, y);
            }
        }
    }

    assert(sz-strip_size <= DIMA(strip_size));
    assert(idx-index <= DIMA(index));

   
    // Generate base vertices
    pt = geom->pts;
    for (side = 0; side < geom->nsides; side++)
    {
        for (x = 0; x < config.subdiv+1; x++)
        {
            for (y = 0; y < config.subdiv+1; y++)
            {
                MapToSide(&cube_planes[side],
                          (FLT)x/config.subdiv, (FLT)y/config.subdiv,
                          pt);
                pt++;
            }
        }
    }

    assert(pt-pts <= DIMA(pts));

    geom->total_pts = geom->nsides*side_pts;
}


/******************************Public*Routine******************************\
*
* InitTetra
*
* Initialize the tetrahedron's geometry
*
\**************************************************************************/

#define TETRA_SIDES 4

#define SQRT3 1.73205f
#define SQRT3_2 (SQRT3/2.0f)
#define SQRT3_3 (SQRT3/3.0f)
#define SQRT3_6 (SQRT3/6.0f)
#define SQRT3_12 (SQRT3/12.0f)

#define TETRA_BASE (-SQRT3/8.0f)

PLANE_MAP tetra_planes[TETRA_SIDES] =
{
    -0.5f, TETRA_BASE, SQRT3_6,
    1.0f, 0.0f, 0.0f, 0.0f, SQRT3_2, -SQRT3_6,
    
    0.0f, TETRA_BASE, -SQRT3_3,
    -0.5f, 0.0f, SQRT3_2, 0.25f, SQRT3_2, SQRT3_12,
    
    0.5f, TETRA_BASE, SQRT3_6,
    -0.5f, 0.0f, -SQRT3_2, -0.25f, SQRT3_2, SQRT3_12,
    
    0.5f, TETRA_BASE, SQRT3_6,
    -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -SQRT3_2
};

void InitTetra(GEOMETRY *geom)
{
    int side, x, y;
    PT3 *pt;
    int *sz, *idx;
    int side_pts;
    int base_pt;
    int row_pts;
    FLT fx;

    side_pts = (config.subdiv+2)*(config.subdiv+1)/2;
    
    geom->nsides = TETRA_SIDES;
    geom->pts = &pts[0];
    geom->npts = &npts[0];
    geom->normals = &normals[0];

    geom->min_sf = -1.1f;
    geom->max_sf = 5.2f;
    geom->sf_inc = 0.05f;
    geom->init_sf = 3.75f;

    // Generate triangle strip data
    sz = &strip_size[0];
    idx = &index[0];
    base_pt = 0;
    for (side = 0; side < geom->nsides; side++)
    {
        geom->sides[side].nstrips = config.subdiv;
        geom->sides[side].strip_size = sz;
        geom->sides[side].strip_index = idx;

        for (x = 0; x < config.subdiv; x++)
        {
            row_pts = config.subdiv-x+1;
            *sz++ = row_pts*2-1;

            *idx++ = base_pt;
            for (y = 0; y < row_pts-1; y++)
            {
                *idx++ = base_pt+row_pts+y;
                *idx++ = base_pt+1+y;
            }

            base_pt += row_pts;
        }

        base_pt++;
    }

    assert(sz-strip_size <= DIMA(strip_size));
    assert(idx-index <= DIMA(index));

    // Generate base vertices
    pt = geom->pts;
    for (side = 0; side < geom->nsides; side++)
    {
        for (x = 0; x < config.subdiv+1; x++)
        {
            fx = (FLT)x/config.subdiv;
            for (y = 0; y < config.subdiv-x+1; y++)
            {
                MapToSide(&tetra_planes[side],
                          fx+(FLT)y/(config.subdiv*2),
                          (FLT)y/config.subdiv,
                          pt);
                pt++;
            }
        }
    }

    assert(pt-pts <= DIMA(pts));

    geom->total_pts = geom->nsides*side_pts;
}

/******************************Public*Routine******************************\
*
* InitPyramids
*
* Initializes double pyramid geometry
*
\**************************************************************************/

#define PYRAMIDS_SIDES 8

PLANE_MAP pyramids_planes[PYRAMIDS_SIDES] =
{
    -0.5f, 0.0f,  0.5f,  1.0f, 0.0f,  0.0f,  0.0f,  0.5f, -0.5f,
     0.5f, 0.0f,  0.5f, -1.0f, 0.0f,  0.0f,  0.0f, -0.5f, -0.5f,
     0.5f, 0.0f,  0.5f,  0.0f, 0.0f, -1.0f, -0.5f,  0.5f,  0.0f,
     0.5f, 0.0f, -0.5f,  0.0f, 0.0f,  1.0f, -0.5f, -0.5f,  0.0f,
     0.5f, 0.0f, -0.5f, -1.0f, 0.0f,  0.0f,  0.0f,  0.5f,  0.5f,
    -0.5f, 0.0f, -0.5f,  1.0f, 0.0f,  0.0f,  0.0f, -0.5f,  0.5f,
    -0.5f, 0.0f, -0.5f,  0.0f, 0.0f,  1.0f,  0.5f,  0.5f,  0.0f,
    -0.5f, 0.0f,  0.5f,  0.0f, 0.0f, -1.0f,  0.5f, -0.5f,  0.0f
};

void InitPyramids(GEOMETRY *geom)
{
    int side, x, y;
    PT3 *pt;
    int *sz, *idx;
    int side_pts;
    int base_pt;
    int row_pts;
    FLT fx;

    side_pts = (config.subdiv+2)*(config.subdiv+1)/2;
    
    geom->nsides = PYRAMIDS_SIDES;
    geom->pts = &pts[0];
    geom->npts = &npts[0];
    geom->normals = &normals[0];

    geom->min_sf = -1.1f;
    geom->max_sf = 5.2f;
    geom->sf_inc = 0.05f;
    geom->init_sf = 3.0f;

    // Generate triangle strip data
    sz = &strip_size[0];
    idx = &index[0];
    base_pt = 0;

    for (side = 0; side < geom->nsides; side++) {
        geom->sides[side].nstrips = config.subdiv;
        geom->sides[side].strip_size = sz;
        geom->sides[side].strip_index = idx;
            
        for (x = 0; x < config.subdiv; x++) {
            row_pts = config.subdiv-x+1;
            *sz++ = row_pts*2-1;

            *idx++ = base_pt;
            for (y = 0; y < row_pts-1; y++) {
                *idx++ = base_pt+row_pts+y;
                *idx++ = base_pt+1+y;
            }

            base_pt += row_pts;
        }
            
        base_pt++;
    }

    assert(sz-strip_size <= DIMA(strip_size));
    assert(idx-index <= DIMA(index));

    // Generate base vertices
    pt = geom->pts;
    for (side = 0; side < geom->nsides; side++)
    {
        for (x = 0; x < config.subdiv+1; x++)
        {
            fx = (FLT)x/config.subdiv;
            for (y = 0; y < config.subdiv-x+1; y++)
            {
                MapToSide(&pyramids_planes[side],
                          fx+(FLT)y/(config.subdiv*2),
                          (FLT)y/config.subdiv,
                          pt);
                pt++;
            }
        }
    }

    assert(pt-pts <= DIMA(pts));

    geom->total_pts = geom->nsides*side_pts;
}

/******************************Public*Routine******************************\
*
* InitCylinder
*
* Initializes the cylinder geometry
*
\**************************************************************************/

void InitCylinder(GEOMETRY *geom)
{
    int side, x, y;
    PT3 *pt;
    int *sz, *idx;
    int base_pt;
    int row_pts;
    FLT fx, fz;
    double ang;

    geom->nsides = 1;
    geom->pts = &pts[0];
    geom->npts = &npts[0];
    geom->normals = &normals[0];

    geom->min_sf = -2.5f;
    geom->max_sf = 8.5f;
    geom->sf_inc = 0.05f;
    geom->init_sf = 2.1f;

    // Generate triangle strip data
    // If version 1.1 then allocate the index buffer for glDrawElements
    sz = &strip_size[0];
    idx = &index[0];
    side = 0;
    geom->sides[side].nstrips = config.subdiv;
    geom->sides[side].strip_size = sz;
    geom->sides[side].strip_index = idx;
    
    row_pts = config.subdiv+1;
    base_pt = 0;
    for (x = 0; x < config.subdiv; x++) {
        *sz++ = row_pts*2;
        
        for (y = 0; y < row_pts; y++) {
            // Wrap around at the edge so the cylinder normals
            // are properly averaged
            if (x == config.subdiv-1) {
                *idx++ = y;
            }
            else {
                *idx++ = base_pt+row_pts+y;
            }
            *idx++ = base_pt+y;
        }

        base_pt += row_pts;
    }
    
    assert(sz-strip_size <= DIMA(strip_size));
    assert(idx-index <= DIMA(index));

    // Generate base vertices
    pt = geom->pts;
    ang = 0;
    for (x = 0; x < config.subdiv; x++)
    {
        fx = (FLT)cos(ang)*0.5f;
        fz = (FLT)sin(ang)*0.5f;
        for (y = 0; y < config.subdiv+1; y++)
        {
            pt->x = fx;
            pt->y = (FLT)y/config.subdiv-0.5f;
            pt->z = fz;
            pt++;
        }
        ang += (2*PI)/config.subdiv;
    }

    assert(pt-pts <= DIMA(pts));

    geom->total_pts = geom->nsides*(config.subdiv+1)*config.subdiv;
}

/******************************Public*Routine******************************\
*
* InitSpring
*
* Initializes the spring geometry
*
\**************************************************************************/

#define SPRING_RADIUS 0.1f
#define SPRING_CENTER (0.5f-SPRING_RADIUS)

void InitSpring(GEOMETRY *geom)
{
    int side, x, y;
    PT3 *pt;
    int *sz, *idx;
    double ang_center, ang_surf;
    FLT cs, sn;
    FLT rad;
    PLANE_MAP plane;
    int spin_pts;
    int row_pts;

    geom->nsides = 1;
    geom->pts = &pts[0];
    geom->npts = &npts[0];
    geom->normals = &normals[0];

    geom->min_sf = -2.2f;
    geom->max_sf = 0.2f;
    geom->sf_inc = 0.05f;
    geom->init_sf = 1.0f;

    // Generate triangle strip data
    // If version 1.1 then allocate the index buffer for glDrawElements
    sz = &strip_size[0];
    idx = &index[0];
    side = 0;
    geom->sides[side].nstrips = config.subdiv;
    geom->sides[side].strip_size = sz;
    geom->sides[side].strip_index = idx;
    
    row_pts = config.subdiv;
    spin_pts = 4*config.subdiv+1;
    for (x = 0; x < config.subdiv; x++) {
        *sz++ = spin_pts*2;

        for (y = 0; y < spin_pts; y++) {
            *idx++ = x+row_pts*y;
            // Wrap around at the edge so the cylindrical surface
            // of the tube is seamless.  Without this the normal
            // averaging would be incorrect and a seam would be visible
            if (x == config.subdiv-1) {
                *idx++ = row_pts*y;
            }
            else {
                *idx++ = x+row_pts*y+1;
            }
        }
    }
    
    assert(sz-strip_size <= DIMA(strip_size));
    assert(idx-index <= DIMA(index));

    // Generate base vertices
    pt = geom->pts;
    ang_center = 0;
    plane.y_axis.x = 0.0f;
    plane.y_axis.y = SPRING_RADIUS;
    plane.y_axis.z = 0.0f;
    plane.x_axis.y = 0.0f;
    for (x = 0; x < spin_pts; x++)
    {
        cs = (FLT)cos(ang_center);
        sn = (FLT)sin(ang_center);
        rad = 0.5f-(FLT)x/(spin_pts-1)*(SPRING_CENTER/2);
        plane.base.x = cs*rad;
        plane.base.y = -0.5f+(FLT)x/(spin_pts-1);
        plane.base.z = sn*rad;
        plane.x_axis.x = cs*SPRING_RADIUS;
        plane.x_axis.z = sn*SPRING_RADIUS;

        ang_surf = 0;
        for (y = 0; y < config.subdiv; y++)
        {
            MapToSide(&plane,
                      (FLT)cos(ang_surf), (FLT)sin(ang_surf),
                      pt);
            pt++;
            ang_surf += (2*PI)/config.subdiv;
        }
        
        ang_center += (4*PI)/(spin_pts-1);
    }

    assert(pt-pts <= DIMA(pts));

    geom->total_pts = geom->nsides*spin_pts*config.subdiv;
}

/******************************Public*Routine******************************\
*
* DrawGeom
*
* Draw the current geometry
*
\**************************************************************************/

void DrawGeom(GEOMETRY *geom)
{
    int side, strip, k;
    int *idx, idc, idxv;

    if (config.smooth_colors)
    {
        glShadeModel(GL_SMOOTH);
    }
    else
    {
        glShadeModel(GL_FLAT);
    }

    if (config.color_pick == ID_COL_SINGLE)
    {
        glMaterialfv(config.two_sided, GL_DIFFUSE, solid_cols);
    }

    if (!(bOgl11 && !bCheckerOn)) {
        for (side = 0; side < geom->nsides; side++) {
            if (config.color_pick == ID_COL_PER_SIDE) {
                glMaterialfv(config.two_sided, GL_DIFFUSE, side_cols[side]);
            }

            idx = geom->sides[side].strip_index;
            for (strip = 0; strip < geom->sides[side].nstrips; strip++) {
                glBegin(GL_TRIANGLE_STRIP);

                for (idc = 0; idc < geom->sides[side].strip_size[strip]; 
                     idc++) {
                    idxv = *idx++;

                    assert(idxv >=0 && idxv < geom->total_pts);

                    if (config.color_pick == ID_COL_CHECKER) {
                        if (config.triangle_colors) {
                            glMaterialfv(config.two_sided, GL_DIFFUSE,
                                         checker_cols[side][(idc+1)/2+strip &
                                                           1]);
                        }
                        else {
                            glMaterialfv(config.two_sided, GL_DIFFUSE,
                                         checker_cols[side][idc/2+strip & 1]);
                        }
                    }
                
                    glNormal3fv((GLfloat *)&geom->normals[idxv]);
                    glVertex3fv((GLfloat *)&geom->npts[idxv]);
                }

                glEnd();
            }
        }
    } else {
        k = 0;
        for (side = 0; side < geom->nsides; side++) {
            if (config.color_pick == ID_COL_PER_SIDE)
                glMaterialfv(config.two_sided, GL_DIFFUSE, side_cols[side]);

            glDrawElements (GL_TRIANGLES, geom->sides[side].num_eles, 
                            GL_UNSIGNED_INT, &(geom->sides[side].dBuf[0]));
            k += geom->sides[side].num_eles;
        }
    }    
}

/******************************Public*Routine******************************\
*
* ComputeAveragedNormals
*
* Compute face-averaged normals for each vertex
*
\**************************************************************************/

void ComputeAveragedNormals(GEOMETRY *geom)
{
    int side, strip;
    int *sz;
    int *idx, idx1, idx2, idx3;
    int tc, idc;
    PT3 v1, v2, n1;
    
    memset(geom->normals, 0, sizeof(PT3)*geom->total_pts);
    
    for (side = 0; side < geom->nsides; side++)
    {
        idx = geom->sides[side].strip_index;
        sz = geom->sides[side].strip_size;
        for (strip = 0; strip < geom->sides[side].nstrips; strip++)
        {
            idx1 = *idx++;
            idx2 = *idx++;

            assert(idx1 >= 0 && idx1 < geom->total_pts &&
                   idx2 >= 0 && idx2 < geom->total_pts);
            
            tc = (*sz++)-2;
            for (idc = 0; idc < tc; idc++)
            {
                idx3 = *idx++;

                assert(idx3 >= 0 && idx3 < geom->total_pts);
                
                V3Sub(&geom->npts[idx3], &geom->npts[idx1], &v1);
                V3Sub(&geom->npts[idx2], &geom->npts[idx1], &v2);
                V3Cross(&v1, &v2, &n1);
                // Triangle strip ordering causes half of the triangles
                // to be oriented oppositely from the others
                // Those triangles need to have their normals flipped
                // so the whole triangle strip has consistent normals
                if ((idc & 1) == 0)
                {
                    n1.x = -n1.x;
                    n1.y = -n1.y;
                    n1.z = -n1.z;
                }
                
                V3Add(&geom->normals[idx1], &n1, &geom->normals[idx1]);
                V3Add(&geom->normals[idx2], &n1, &geom->normals[idx2]);
                V3Add(&geom->normals[idx3], &n1, &geom->normals[idx3]);

                idx1 = idx2;
                idx2 = idx3;
            }
        }
    }
}

/******************************Public*Routine******************************\
*
* UpdatePts
*
* Project the point array through a sphere according to the given scale factor
*
\**************************************************************************/

void UpdatePts(GEOMETRY *geom, FLT sf)
{
    int pt;
    FLT f, *vl;
    PT3 *v;
    PT3 *p;

    vl = vlen;
    p = &geom->pts[0];
    v = &geom->npts[0];
    for (pt = 0; pt < geom->total_pts; pt++)
    {
        f = (*vl++)*sf+1;
        v->x = p->x*f;
        v->y = p->y*f;
        v->z = p->z*f;
        p++;
        v++;
    }

    ComputeAveragedNormals(geom);
}

