/*
 * gears.c - Create a set of gears.  Each gear face has 144 vertices, and
 *   contains concavities.  Note that the first 3 vertices of all polygons
 *   define the two edges of a convex section of the polygon.  Background
 *   square floor is reflective.  Some gears are clipped.
 *   Five light sources.
 *
 * Version:  2.2 (11/17/87)
 * Author:  Eric Haines, 3D/Eye, Inc.
 *
 * size_factor determines the number of polygons output.
 *     Total gears = SF**3:  concave polygons = 2 * SF**3
 *     rectangles = 4*TEETH * SF**3
 *
 * size_factor   # gears # gear faces    # rectangles
 *       1          1          2           144
 *       2          8         16          1152
 *       3         27         54          3888
 *       4         64        128          9216
 */

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

/* These may be read from the command line */
static int size_factor = 4;
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 */

/* define number of teeth on a gear - must be a multiple of 4 */
#define	TEETH			36
/* define ratio of radius taken up by teeth and the gear thickness */
/* the outer radius is made slightly smaller that the full radius to create
 * a finite separation between intermeshing gears.  This gets rid of the bug
 * of having two surfaces occupy exactly the same space.  Note that if these
 * are changed, the gears may interpenetrate.
 */
#define	OUTER_EDGE_RATIO	0.995
#define	INNER_EDGE_RATIO	0.9
#define	EDGE_DIFF		( 1.0 - INNER_EDGE_RATIO )

/* ratio of width of gear to thickness */
#define	DEPTH_RATIO		0.1

/* define ratio of radius taken up by teeth and the gear thickness */
#define EDGE_RATIO   0.1

/* Create gear tooth */
void
create_tooth(gear_angle, tooth_angle, center, outer_pt, inner_pt, edge_pts)
    double gear_angle, tooth_angle;
    COORD3 center, outer_pt, inner_pt;
    COORD3 edge_pts[];
{
    MATRIX mx;

    lib_create_rotate_matrix(mx, Z_AXIS, gear_angle - 0.19 * tooth_angle);
    lib_transform_vector(edge_pts[0], outer_pt, mx);
    ADD2_COORD3( edge_pts[0], center);
    lib_create_rotate_matrix(mx, Z_AXIS, gear_angle + 0.19 * tooth_angle);
    lib_transform_vector(edge_pts[1], outer_pt, mx);
    ADD2_COORD3(edge_pts[1], center);
    lib_create_rotate_matrix(mx, Z_AXIS, gear_angle + 0.3 * tooth_angle);
    lib_transform_vector(edge_pts[2], inner_pt, mx);
    ADD2_COORD3(edge_pts[2], center);
    lib_create_rotate_matrix(mx, Z_AXIS, gear_angle + 0.7 * tooth_angle);
    lib_transform_vector(edge_pts[3], inner_pt, mx);
    ADD2_COORD3(edge_pts[3], center);
}

/* Create gear */
void
create_gear(center, offset_angle, outer_radius, inner_radius, thickness)
    COORD3 center;
    double offset_angle, outer_radius, inner_radius, thickness;
{
    int next_side, num_side, num_teeth ;
    double gear_angle, tooth_angle ;
    COORD3 side_pts[4], gear_pts[4*TEETH], outer_pt, inner_pt ;

    SET_COORD3( outer_pt, outer_radius, 0.0, 0.0 ) ;
    SET_COORD3( inner_pt, inner_radius, 0.0, 0.0 ) ;

    tooth_angle = 2.0 * PI / (double)TEETH ;

    /* output gear top */
    for (num_teeth=0;num_teeth<TEETH;num_teeth++) {
	gear_angle = offset_angle +
		2.0 * PI * (double)num_teeth / (double)TEETH;
	create_tooth(gear_angle, tooth_angle, center, outer_pt, inner_pt,
		(COORD3 *)gear_pts[num_teeth*4]);
    }
    lib_output_polygon(4*TEETH, gear_pts);

    /* output teeth */
    for (num_side=0;num_side<4*TEETH;num_side++) {
	next_side = (num_side + 1) % ( 4 * TEETH);
	COPY_COORD3(side_pts[0], gear_pts[num_side]);
	COPY_COORD3(side_pts[1], gear_pts[num_side]);
	side_pts[1][Z] -= thickness;
	COPY_COORD3(side_pts[2], gear_pts[next_side]);
	side_pts[2][Z] -= thickness;
	COPY_COORD3(side_pts[3], gear_pts[next_side]);
	lib_output_polygon(4, side_pts);
    }

    /* output gear bottom */
    outer_pt[Z] = inner_pt[Z] = -thickness;
    for (num_teeth=0;num_teeth<TEETH;num_teeth++) {
	gear_angle = offset_angle -
		2.0 * PI * (double)num_teeth / (double)TEETH;
	create_tooth(gear_angle, -tooth_angle, center, outer_pt, inner_pt,
		(COORD3 *)gear_pts[num_teeth*4]);
    }

    lib_output_polygon(4*TEETH, gear_pts);
}

int
main(argc,argv)
    int argc ;
    char *argv[] ;
{
    COORD4 light;
    COORD3 back_color, gear_color;
    COORD3 center, floor[4], offset, zero_pt;
    COORD3 from, at, up;
    double angle, color_scale, outer_radius, thickness, lscale;
    int     ix, iy, iz ;

    PLATFORM_INIT(SPD_GEARS);

    /* 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("Gears.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);

    lib_set_polygonalization(3, 3);

    /* output background color - black */
    /* NOTE: Do this BEFORE lib_output_viewpoint(), for display_init() */
    SET_COORD3(back_color, 0.0, 0.0, 0.0);
    lib_output_background_color(back_color);

    /* output viewpoint */
    SET_COORD3(from, -1.1, -2.1, 2.6);
    SET_COORD3(at, 0.0, 0.0, 0.0);
    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 sources */
    lscale = (raytracer_format == OUTPUT_NFF ||
	      raytracer_format == OUTPUT_RTRACE ? 1.0 : 1.0 / sqrt(5.0));
    SET_COORD4(light, 2.0, 4.0, 4.0, lscale);
    lib_output_light(light);
    SET_COORD4(light, -2.0, 4.0, 3.0, lscale);
    lib_output_light(light);
    SET_COORD4(light, 2.0, -2.5, 2.5, lscale);
    lib_output_light(light);
    SET_COORD4(light, -1.0, -4.0, 2.0, lscale);
    lib_output_light(light);

    /* just behind the eye */
    SET_COORD4(light, -1.111, -2.121, 2.626, lscale);
    lib_output_light(light);

    /* output floor polygon - off white */
    SET_COORD3(back_color, 1.0, 0.85, 0.7);
    lib_output_color(NULL, back_color, 0.0, 0.3, 0.6, 0.3, 5.0, 0.0, 0.0);
    SET_COORD3(floor[0],  2.0,  2.0, 0.0);
    SET_COORD3(floor[1], -2.0,  2.0, 0.0);
    SET_COORD3(floor[2], -2.0, -2.0, 0.0);
    SET_COORD3(floor[3],  2.0, -2.0, 0.0);
    lib_output_polygon(4, floor);

    outer_radius = 1.0/
	     ((double)size_factor-(double)(size_factor-1)*EDGE_DIFF/2.0);

    /* calculate first gear center */
    zero_pt[X] = zero_pt[Y] = -1.0 + outer_radius;
    zero_pt[Z] = 1.0;

    /* calculate offset */
    offset[X] = offset[Y] = outer_radius * (2.0 - EDGE_DIFF);
    offset[Z] = -1.0 / (double)size_factor;

    /* create gears */
    for (iz=0;iz<size_factor;iz++) {
	center[Z] = zero_pt[Z] + (double)iz * offset[Z] ;
	for (iy=0;iy<size_factor;iy++) {
	    center[Y] = zero_pt[Y] + (double)iy * offset[Y] ;
	    for (ix=0;ix<size_factor;ix++) {
		center[X] = zero_pt[X] + (double)ix * offset[X] ;
		/* output pseudo-random gear color */
		SET_COORD3(gear_color,
			   0.01 + FRACTION((double)(ix*3+iy*2+iz+1)*5.0/7.0),
			   0.01 + FRACTION((double)(iy*3+iz*2+ix+1)*3.0/7.0),
			   0.01 + FRACTION((double)(iz*3+ix*2+iy+1)*2.0/7.0));
		color_scale = MAX(gear_color[0],gear_color[1]);
		color_scale = MAX(color_scale,gear_color[2]);
		gear_color[X] /= color_scale;
		gear_color[Y] /= color_scale;
		gear_color[Z] /= color_scale;
		if ((ix*4 + iy*2 + iz ) % 5 == 0)
		   lib_output_color(NULL, gear_color, 0.0, 0.2, 0.0, 0.0, 0.0,
				    0.8, 1.1);
		else
		   lib_output_color(NULL, gear_color, 0.0, 1.0, 0.0, 0.0, 0.0,
				    0.0, 0.0);

		/* output gear */
		angle = PI * (double)((ix+iy+iz) % 2) / (double)(TEETH);
		thickness = MIN(DEPTH_RATIO, 1.0 / (2.0 * (double)size_factor));
		create_gear(center, angle, OUTER_EDGE_RATIO * outer_radius,
			    (1.0 - EDGE_DIFF) * outer_radius, thickness);
	    }
	}
    }

    /* 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;
}
