/*
 * balls.c - Also known as "sphereflake".  Create a set of shiny spheres, with
 *	each sphere blooming sets of 9 more spheres with 1/3rd radius.
 *	A square floor polygon is added.  Three light sources.
 *
 * Author:  Eric Haines, 3D/Eye, Inc.
 *
 * size_factor determines the number of objects output.
 *	Total spheres = sum of n=0,SF of (9**SF).
 *
 *	size_factor	# spheres	# squares
 *	     1		    10		     1
 *	     2		    91		     1
 *	     3		   820		     1
 *
 *	     4		  7381		     1
 */

#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 */

static	COORD4	objset[9] ;

/*
 * Output the parent sphere, then output the children of the sphere.
 * Uses global 'objset'.
 */
void
output_object(depth, center, direction)
    int depth;
    COORD4 center, direction;
{
    double  angle;
    COORD4  axis, z_axis;
    COORD4  child_pt, child_dir;
    MATRIX  mx;
    long    num_vert;
    double  scale;

    PLATFORM_MULTITASK();

    /* output sphere at location & radius defined by center */
    lib_output_sphere(center, output_format);

    /* check if children should be generated */
    if (depth > 0) {
	--depth ;

	/* rotation matrix to new axis from +Z axis */
	if ( direction[Z] >= 1.0 ) {
	    /* identity matrix */
	    lib_create_identity_matrix(mx);
	    }
	else if ( direction[Z] <= -1.0 ) {
	    lib_create_rotate_matrix(mx, Y_AXIS, PI);
	    }
	else {
	    SET_COORD3(z_axis, 0.0, 0.0, 1.0);
	    CROSS(axis, z_axis, direction);
	    lib_normalize_vector(axis);
	    angle = acos((double)DOT_PRODUCT(z_axis, direction));
	    lib_create_axis_rotate_matrix(mx, axis, angle);
	    }

	/* scale down location of new spheres */
	scale = center[W] * (1.0 + direction[W]);

	for ( num_vert = 0 ; num_vert < 9 ; ++num_vert ) {
	    lib_transform_coord(child_pt, objset[num_vert], mx);
	    child_pt[X] = child_pt[X] * scale + center[X];
	    child_pt[Y] = child_pt[Y] * scale + center[Y];
	    child_pt[Z] = child_pt[Z] * scale + center[Z];
	    /* scale down radius */
	    child_pt[W] = center[W] * direction[W];
	    SUB3_COORD3( child_dir, child_pt, center);
	    child_dir[X] /= scale;
	    child_dir[Y] /= scale;
	    child_dir[Z] /= scale;
	    child_dir[W] = direction[W];
	    output_object(depth, child_pt, child_dir);
	}
    }
}

/* Create the set of 9 vectors needed to generate the sphere set. */
/* Uses global 'objset' */
void
create_objset()
{
    COORD4  axis, temp_pt, trio_dir[3];
    double  dist;
    MATRIX  mx;
    long    num_set, num_vert;

    dist = 1.0 / sqrt((double)2.0);

    SET_COORD4(trio_dir[0], dist, dist,   0.0, 0.0);
    SET_COORD4(trio_dir[1], dist,  0.0, -dist, 0.0);
    SET_COORD4(trio_dir[2],  0.0, dist, -dist, 0.0);

    SET_COORD3(axis, 1.0, -1.0, 0.0);
    lib_normalize_vector(axis);
    lib_create_axis_rotate_matrix(mx, axis,
			          asin((double)(2.0/sqrt((double)6.0))));

    for (num_vert=0;num_vert<3;++num_vert) {
	lib_transform_coord(temp_pt, trio_dir[num_vert], mx);
	COPY_COORD4(trio_dir[num_vert], temp_pt);
    }

    for (num_set=0;num_set<3;++num_set) {
	lib_create_rotate_matrix(mx, Z_AXIS, num_set*2.0*PI/3.0);
	for (num_vert=0;num_vert<3;++num_vert) {
	    lib_transform_coord(objset[num_set*3+num_vert],
				trio_dir[num_vert], mx);
	}
    }
}

int
main(argc,argv)
    int argc ;
    char *argv[] ;
{
    COORD3 back_color, obj_color;
    COORD3 backg[5], bvec;
    COORD3 from, at, up;
    COORD4 light;
    COORD4 center_pt, direction;
    double radius, lscale;

    PLATFORM_INIT(SPD_BALLS);

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

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

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

    lib_set_output_file(stdout_file);

    /* set radius of sphere which would enclose entire object */
    radius = 1.0 ;

    /* output background color - UNC sky blue */
    /* NOTE: Do this BEFORE lib_output_viewpoint(), for display_init() */
    SET_COORD3(back_color, 0.078, 0.361, 0.753);
    lib_output_background_color(back_color);

    /* output viewpoint */
    SET_COORD3(from, 2.1, 1.3, 1.7);
    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, 0.01, 512, 512);

    /* For raytracers that don't scale the light intensity, we will do
       it for them */
    lscale = ( ((raytracer_format == OUTPUT_NFF) ||
	       (raytracer_format == OUTPUT_RTRACE)) ? 1.0 : 1.0 / sqrt(3.0));

    /* output light sources */
    SET_COORD4(light, 4.0, 3.0, 2.0, lscale);
    lib_output_light(light);
    SET_COORD4(light, 1.0, -4.0, 4.0, lscale);
    lib_output_light(light);
    SET_COORD4(light, -3.0, 1.0, 5.0, lscale);
    lib_output_light(light);

    /* output floor polygon - beige */
    SET_COORD3(back_color, 1.0, 0.75, 0.33);
    lib_output_color(NULL, back_color, 0.2, 0.8, 0.0, 0.0, 0.0, 0.0, 1.0);
    SET_COORD3(bvec, 12.0 * radius, 12.0 * radius, -radius / 2.0);
    SET_COORD3(backg[0],  bvec[X],  bvec[Y], bvec[Z]);
    SET_COORD3(backg[1], -bvec[X],  bvec[Y], bvec[Z]);
    SET_COORD3(backg[2], -bvec[X], -bvec[Y], bvec[Z]);
    SET_COORD3(backg[3],  bvec[X], -bvec[Y], bvec[Z]);
    SET_COORD3(backg[4],  bvec[X],  bvec[Y], bvec[Z]);
    lib_output_polygon(4, backg);

    /* set up object color - mirrored */
    SET_COORD3(obj_color, 1.0, 0.9, 0.7);
    lib_output_color(NULL, obj_color, 0.0, 0.5, 0.5, 0.5, 5.0, 0.0, 1.0);

    /* create set of spawned points */
    create_objset();

    /* compute and output object */
    SET_COORD4(center_pt, 0.0, 0.0, 0.0, radius / 2.0);
    SET_COORD4(direction, 0.0, 0.0, 1.0, 1.0/3.0);
    output_object(size_factor, center_pt, direction);

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