/*
 * mount.c - creates a fractal mountain, using Carpenter's method with a
 *	different extension to square grids.  A pyramid of 4 glass spheres
 *	is added in front of the mountain.  One light source.
 *
 *	NOTE: the hashing function used to generate the database originally is
 *	faulty.  The function causes repetition to occur within the fractal
 *	mountain (obviously not very fractal behavior!).  A new hashing
 *	function is included immediately after the old one:  merely define
 *	NEW_HASH if you want to use a better hashing function.  To perform ray
 *	tracing comparison tests you should still use the old, faulty database
 *	(it may have repetition, but it's still a good test image).
 *
 * Author:  Eric Haines, 3D/Eye, Inc.
 *
 * size_factor determines the number of objects output.
 *	Total triangular polygons = 2 * (4**size_factor)
 *
 *	size_factor	# triangles	# spheres
 *	     1		     8		     4
 *	     2		    32		     4
 *	     3		   128		     4
 *
 *	     6		  8192		     4
 */

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

/* Determine which raytracer we will use */
static int raytracer_format = OUTPUT_RT_DEFAULT;
/* output format determines if polygons or true surfaces are used */
static int output_format = OUTPUT_CURVES;


/* Complexity of the image */
static int size_factor = 6;

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

/* to use the corrected hashing function, uncomment this next line */
/* #define NEW_HASH */

/* fractal dimension - affects variance of z.  Between 2 and 3 */
#define	FRACTAL_DIMENSION	2.2
/* change MOUNTAIN_NO to get a different mountain */
#define	MOUNTAIN_NO		21

/* lower left corner and width of mountain definitions */
#define	X_CORNER	-1.0
#define	Y_CORNER	-1.0
#define	WIDTH		 2.0

#ifndef NEW_HASH

/* Hashing function to get a seed for the random number generator. */
/* This is the old, buggy hashing function - use it if you wish to
 * obtain the same image as in the November 1987 IEEE CG&A article. */
#define	hash_rand(A,B,C)	( ( (((unsigned long)(A))<<(23-(C))) +	\
				    (((unsigned long)(B))<<(15-(C)))	\
				  + (((unsigned long)(A))<<(7-(C))) ) & 0xffff)
#else

/* New, corrected hashing function.  Use for a true fractal mountain */
/* 134456 is M1 in routine lib_gauss_rand() */
#define	hash_rand(A,B,C)	( ( C <= 15 ) ?				\
				  ( ABSOLUTE(				\
				    (((unsigned long)(A))<<(31-(C))) +	\
				    (((unsigned long)(B))<<(15-(C))) )	\
				  % 134456 ) :				\
				  ( ABSOLUTE(				\
				    (((unsigned long)(A))<<(31-(C))) +	\
				    (((unsigned long)(B))>>((C)-15)) )	\
				  % 134456 ) )
				  )
#endif

static	double  Roughness ;

/* create a pyramid of crystal spheres */
void
create_spheres(center)
    COORD4 center;
{
    int i;
    double angle;
    COORD3 axis, pt, new_pt;
    COORD4 sphere;
    MATRIX mx;

    SET_COORD3(axis, 1.0, 1.0, 0.0);
    (void)lib_normalize_vector(axis);
    angle = acos((double)(-1.0/3.0));

    /* set center of pyramid */
    SET_COORD3(pt, 0.0, 0.0, center[W] * sqrt((double)(3.0/2.0)));

    COPY_COORD4(sphere, center);
    ADD2_COORD3(sphere, pt);
    lib_output_sphere(sphere, output_format);

    lib_create_axis_rotate_matrix(mx, axis, angle);
    lib_transform_vector(new_pt, pt, mx);

    for (i = 0; i < 3; i++) {
	lib_create_rotate_matrix(mx, Z_AXIS, (double)i * 2.0 * PI / 3.0);
	lib_transform_vector(sphere, new_pt, mx);
	ADD2_COORD3(sphere, center);
	lib_output_sphere(sphere, output_format);
    }
}

/*
 * Build mountain section.  If at width > 1, split quadrilateral into four
 * parts.  Else if at width == 1, output quadrilateral as two triangles.
 */
void
grow_mountain(fnum_pts, width, ll_x, ll_y, ll_fz, lr_fz, ur_fz, ul_fz)
    double fnum_pts;
    int width;
    int ll_x;
    int ll_y ;
    double ll_fz;
    double lr_fz;
    double ur_fz;
    double ul_fz;
{
    long iz;
    int half_width, num_tri, num_tri_vert, num_vert;
    double l_fx, r_fx, l_fy, u_fy;
    double lower_fz, right_fz, upper_fz, left_fz, middle_fz;
    double rise_height, hside_length;
    COORD3 tri_vert[3];

    if ( width == 1 ) {
	/* calculate x and y coordinates of corners */
	l_fx = X_CORNER + (double)ll_x * WIDTH / fnum_pts;
	r_fx = X_CORNER + (double)(ll_x+1) * WIDTH / fnum_pts;
	l_fy = Y_CORNER + (double)ll_y * WIDTH / fnum_pts;
	u_fy = Y_CORNER + (double)(ll_y+1) * WIDTH / fnum_pts;

	/* output two triangles for section */
	for (num_tri = 0; num_tri < 2; num_tri++) {
	    for (num_vert = 0; num_vert < 3; num_vert++) {
		num_tri_vert = (num_vert + num_tri * 2) % 4;
		switch (num_tri_vert) {
		    case 0:
			SET_COORD3(tri_vert[num_vert], l_fx, l_fy, ll_fz);
			break;
		    case 1:
			SET_COORD3(tri_vert[num_vert], r_fx, l_fy, lr_fz);
			break;
		    case 2:
			SET_COORD3(tri_vert[num_vert], r_fx, u_fy, ur_fz);
			break;
		    case 3:
			SET_COORD3(tri_vert[num_vert], l_fx, u_fy, ul_fz);
			break;
		}
	    }
	    lib_output_polygon(3, tri_vert);
	}
    } else {
	/* subdivide edges and move in z direction */
	half_width = width>>1;
	hside_length = (double)half_width * WIDTH / fnum_pts;
	rise_height = hside_length * Roughness;

	/* for each midpoint, find z */
	iz = MOUNTAIN_NO + hash_rand(ll_x + half_width, ll_y, size_factor);
	lower_fz = (ll_fz + lr_fz) / 2.0 + rise_height * lib_gauss_rand(iz);
	iz = MOUNTAIN_NO + hash_rand(ll_x + width, ll_y + half_width,
					  size_factor);
	right_fz = ( lr_fz + ur_fz ) / 2.0 + rise_height * lib_gauss_rand(iz);
	iz = MOUNTAIN_NO + hash_rand(ll_x + half_width, ll_y + width,
					  size_factor);
	upper_fz = ( ur_fz + ul_fz ) / 2.0 + rise_height * lib_gauss_rand(iz);
	iz = MOUNTAIN_NO + hash_rand( ll_x, ll_y + half_width, size_factor);
	left_fz = ( ul_fz + ll_fz ) / 2.0 + rise_height * lib_gauss_rand(iz);
	iz = MOUNTAIN_NO + hash_rand(ll_x + half_width, ll_y + half_width,
					  size_factor);
	middle_fz = ( ll_fz + lr_fz + ur_fz + ul_fz ) / 4.0 +
			  1.4142136 * rise_height * lib_gauss_rand(iz);

	/* check subsections for subdivision or output */
	grow_mountain(fnum_pts, half_width, ll_x, ll_y,
			  ll_fz, lower_fz, middle_fz, left_fz);
	grow_mountain(fnum_pts, half_width, ll_x+half_width, ll_y,
			  lower_fz, lr_fz, right_fz, middle_fz);
	grow_mountain(fnum_pts, half_width, ll_x+half_width, ll_y+half_width,
			  middle_fz, right_fz, ur_fz, upper_fz);
	grow_mountain(fnum_pts, half_width, ll_x, ll_y+half_width,
			  left_fz, middle_fz, upper_fz, ul_fz);
    }
}

int
main(argc,argv)
    int argc;
    char *argv[];
{
    int num_pts;
    double ratio;
    COORD3 back_color, obj_color;
    COORD3 from, at, up;
    COORD4 light, center;

    PLATFORM_INIT(SPD_MOUNT);

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

    /* 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, -1.6, 1.6, 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);

    /* output light sources */
    SET_COORD4(light, -100.0, -100.0, 100.0, 1.0);
    lib_output_light(light);

    /* set up crystal sphere color - clear white */
    SET_COORD3(obj_color, 1.0, 1.0, 1.0);
    lib_output_color(NULL, obj_color, 0.0, 0.1, 0.1, 0.4, 5.0, 0.9, 1.5);

    /* output crystal spheres */
    SET_COORD4(center, -0.8, 0.8, 1.00, 0.17);
    create_spheres(center);

    /* set up mountain color - grey */
    SET_COORD3(obj_color, 0.5, 0.45, 0.35);
    lib_output_color(NULL, obj_color, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0);

    /* grow mountain */
    num_pts = 1<<size_factor;
    ratio = 2.0 / exp((double)(log((double)2.0) / (FRACTAL_DIMENSION-1.0)));
    Roughness = sqrt((double)(SQR(ratio) - 1.0));
    grow_mountain((double)num_pts, num_pts, 0, 0, 0.0, 0.0, 0.0, 0.0);

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