/*
 * rings.c - Create objects with 6 pentagonal rings which connect the midpoints
 *	of the edges of a dodecahedron.  A pyramid of these objects is formed,
 *	which the viewer looks upon from the point.  A plane is placed behind
 *	the pyramid for shadows.  Three light sources.
 *
 * Author:  Eric Haines, 3D/Eye, Inc.
 *
 * size_factor determines the number of objects output.
 *	Each object has 30 cylinders and 30 spheres.
 *	Total objects = SF*SF + (SF-1)*(SF-1) + ... + 1 plus 1 backdrop square.
 *	formula for # of spheres or cylinders = 5*SF*(SF+1)*(2*SF+1)
 *
 *	size_factor	# spheres	# cylinders	# squares
 *	     1		    30		      30	     1
 *	     2		   150		     150	     1
 *	     3		   420		     420	     1
 *
 *	     7		  4200		    4200	     1
 */

#include <stdio.h>
#include <math.h>
#include <stdlib.h>	/* atoi */
#include "def.h"
#include "drv.h"	/* display_close() */
#include "lib.h"

static int size_factor = 7 ;
static int raytracer_format = OUTPUT_RT_DEFAULT;
static int output_format    = OUTPUT_CURVES;

#ifdef OUTPUT_TO_FILE
static FILE * stdout_file = NULL;
#else
#define stdout_file stdout
#endif /* OUTPUT_TO_FILE */

/* if spread out is > 1, succeeding layers spread out more */
#define	SPREAD_OUT		1

/* Create the set of 30 points needed to generate the rings */
void
create_dodec( minor_radius, vertex )
    double minor_radius ;
    COORD3 vertex[30] ;
{
    int num_vertex, num_pentagon ;
    double scale, x_rotation, z_rotation ;
    COORD3 temp_vertex ;
    MATRIX x_matrix, z_matrix ;

    /* scale object to fit in a sphere of radius 1 */
    scale = 1.0 / ( 1.0 + minor_radius ) ;

    /*
     * define one pentagon as on the XY plane, with points starting along +X
     * and N fifths of the way around the Z axis.
     */
    for ( num_vertex = 0 ; num_vertex < 5 ; ++num_vertex ) {
	vertex[num_vertex][X] = scale * cos((double)num_vertex * 2.0*PI/5.0 ) ;
	vertex[num_vertex][Y] = scale * sin((double)num_vertex * 2.0*PI/5.0 ) ;
	vertex[num_vertex][Z] = 0.0 ;
	vertex[num_vertex][W] = 1.0 ;
    }

    /*
     * find the rotation angle (in radians) along the X axis:
     * angle between two adjacent dodecahedron faces.
     */
    x_rotation = 2.0 *
		acos( cos( (double)(PI/3.0) ) / sin( (double)(PI/5.0) ) ) ;
    lib_create_rotate_matrix( x_matrix, X_AXIS, x_rotation ) ;

    /*
     * Find each of the other 5 pentagons:  rotate along the X axis,
     * then rotate on the Z axis.
     */
    for ( num_pentagon = 1 ; num_pentagon < 6 ; ++num_pentagon ) {
	/*
	 * find the rotation angle (in radians) along the Z axis:
	 * 1/10th plus N fifths of the way around * 2 * PI.
	 */
	z_rotation = PI*( 2.0*(double)(num_pentagon-1)+1.0 ) / 5.0 ;
	lib_create_rotate_matrix( z_matrix, Z_AXIS, z_rotation ) ;

	for ( num_vertex = 0 ; num_vertex < 5 ; ++num_vertex ) {

	    lib_transform_point( temp_vertex
			       , vertex[num_vertex]
			       , x_matrix
			       ) ;

	    lib_transform_point( vertex[5*num_pentagon+num_vertex]
			       , temp_vertex
			       , z_matrix
			       ) ;
	}
    }
}

int
main(argc,argv)
    int argc;
    char *argv[];
{
    int	prev_elem, num_elem, num_depth, num_objx, num_objz ;
    double radius, spread, y_diff, xz_diff ;
    COORD4 base_pt, apex_pt ;
    COORD3 from, at, up ;
    COORD3 wvec, light ;
    COORD3 back_color, ring_color[6] ;
    COORD3 wall[4], offset, dodec[30] ;
    double lscale;

    PLATFORM_INIT(SPD_RINGS);

    /* Start by defining which raytracer we will be using */
    if ( lib_get_opts( argc, argv, &size_factor, &raytracer_format,
	    &output_format ) ) {
	return 0;
    }

#ifdef OUTPUT_TO_FILE
    /* no stdout, so write to a file! */
    if (raytracer_format == OUTPUT_VIDEO)
	stdout_file = stdout;
    else
	stdout_file = fopen("Rings.out","w");
#endif /* OUTPUT_TO_FILE */
    lib_set_output_file(stdout_file);

    if (raytracer_format == OUTPUT_RTRACE ||
	raytracer_format == OUTPUT_PLG)
	lib_set_raytracer(OUTPUT_DELAYED);
    else
	lib_set_raytracer(raytracer_format);

    radius = 0.07412 ;	/* cone and sphere radius */

    /* calculate spread of objects */
    spread = 1 / sin( (double)( PI/8.0 ) ) ;
    if ( SPREAD_OUT <= spread ) {
	y_diff = spread / SPREAD_OUT ;
	xz_diff = 1.0 ;
    }
    else {
	y_diff = 1.0 ;
	xz_diff = SPREAD_OUT / spread ;
    }

    /* output background color - UNC sky blue */
    /* NOTE: Do this BEFORE lib_output_viewpoint(), for display_init() */
    /* note that the background color should never be seen */
    SET_COORD3( back_color, 0.078, 0.361, 0.753 ) ;
    lib_output_background_color( back_color ) ;

    /* output viewpoint */
    SET_COORD3( from, -1.0, -spread, 0.5 ) ;
    SET_COORD3( at, from[X], from[Y] + 1.0, from[Z] ) ;
    SET_COORD3( up, 0.0, 0.0, 1.0 ) ;
    lib_output_viewpoint( from, at, up, 45.0, 1.0, 1.0, 512, 512);

    /* output light source */
    lscale = (raytracer_format == OUTPUT_NFF ||
	      raytracer_format == OUTPUT_RTRACE ? 1.0 : 1.0/sqrt(3.0));
    SET_COORD4( light, 3.0, -spread, 3.0, lscale ) ;
    lib_output_light( light ) ;
    SET_COORD4( light, -4.0, -spread, 1.0, lscale ) ;
    lib_output_light( light ) ;
    SET_COORD4( light, 2.0, -spread, -4.0, lscale ) ;
    lib_output_light( light ) ;

    /* output wall polygon - white */
    SET_COORD3( back_color, 1.0, 1.0, 1.0 ) ;
    lib_output_color(NULL, back_color, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0);

    /* just spans 45 degree view + 1% */
    wvec[Y] = y_diff * ( size_factor + 1 ) ;
    wvec[X] = wvec[Z] = 1.01 * ( wvec[Y] - from[Y] ) * tan( PI / 8.0 ) ;
    SET_COORD3( wall[0],  wvec[X]+from[X], wvec[Y],  wvec[Z]+from[Z] ) ;
    SET_COORD3( wall[1], -wvec[X]+from[X], wvec[Y],  wvec[Z]+from[Z] ) ;
    SET_COORD3( wall[2], -wvec[X]+from[X], wvec[Y], -wvec[Z]+from[Z] ) ;
    SET_COORD3( wall[3],  wvec[X]+from[X], wvec[Y], -wvec[Z]+from[Z] ) ;
    lib_output_polygon( 4, wall ) ;

    /* set up ring colors - RGB and complements */
    SET_COORD3( ring_color[0], 1.0, 0.0, 0.0 ) ;
    SET_COORD3( ring_color[1], 0.0, 1.0, 0.0 ) ;
    SET_COORD3( ring_color[2], 0.0, 0.0, 1.0 ) ;
    SET_COORD3( ring_color[3], 0.0, 1.0, 1.0 ) ;
    SET_COORD3( ring_color[4], 1.0, 0.0, 1.0 ) ;
    SET_COORD3( ring_color[5], 1.0, 1.0, 0.0 ) ;

    create_dodec( radius, dodec ) ;
    /* radius of osculating cylinders and spheres (no derivation given) */
    base_pt[W] = apex_pt[W] = radius ;

    for ( num_depth = 0 ; num_depth < size_factor ; ++num_depth ) {
	offset[Y] = y_diff * (double)(num_depth+1) ;
	for ( num_objz = 0 ; num_objz <= num_depth ; ++num_objz ) {
	    offset[Z] = xz_diff * (double)(2*num_objz - num_depth) ;
	    for ( num_objx = 0 ; num_objx <= num_depth ; ++num_objx ) {
		offset[X] = xz_diff * (double)(2*num_objx - num_depth) ;
		for ( num_elem = 0 ; num_elem < 30 ; ++num_elem ) {
		    PLATFORM_MULTITASK();
		    COPY_COORD3( base_pt, dodec[num_elem] ) ;
		    ADD2_COORD3( base_pt, offset ) ;
		    if ( num_elem%5 == 0 ) {
			prev_elem = num_elem + 4 ;
			/* new ring beginning - output color */
			lib_output_color(NULL, ring_color[num_elem/5],
					 0.0, 0.5, 0.2, 0.3, 10.0, 0.0, 0.0);
		    }
		    else {
			prev_elem = num_elem - 1 ;
		    }
		    COPY_COORD3( apex_pt, dodec[prev_elem] ) ;
		    ADD2_COORD3( apex_pt, offset ) ;

		    lib_output_cylcone( base_pt, apex_pt, output_format ) ;
		    lib_output_sphere( base_pt, output_format ) ;
		}
	    }
	}
    }

    /* Make sure everything is cleaned up */
    if (raytracer_format == OUTPUT_RTRACE ||
	raytracer_format == OUTPUT_PLG) {

	lib_set_raytracer(raytracer_format);
	lib_flush_definitions();
    }
#ifdef OUTPUT_TO_FILE
    /* no stdout, so close our output! */
    if (stdout_file)
	fclose(stdout_file);
#endif /* OUTPUT_TO_FILE */
    if (raytracer_format == OUTPUT_VIDEO)
	display_close(1);

    PLATFORM_SHUTDOWN();
    return EXIT_SUCCESS ;
}
