/************************************************************************/
/* Recursive update procedure for fractal landscapes					*/
/*																		*/
/* The only procedures needed outside this file are						*/
/*																		*/
/*	make_fold,		called once to initialise the data structs.			*/
/*	next_strip,		each call returns a new strip off the side of the	*/
/*					surface -  you can keep calling this as often as	*/
/*					you want.											*/
/*	free_strip,		get rid of the strip when finished with it.			*/
/*	free_fold,		get rid of the data structs when finished with this	*/
/*					surface.											*/
/*																		*/
/* Apart  from  make_fold  all these routines get their parameters from	*/
/* their  local Fold struct, make_fold initialises all these values and	*/
/* has  to get it right for the fractal to work.  If you want to change	*/
/* the  fractal  dim in mid run you will have to change values at every	*/
/* level.   each  recursive  level  only calls the level below once for	*/
/* every  two  times  it  is  called itself so it will take a number of	*/
/* iterations  for any changes to be notices by the bottom (long length	*/
/* scale) level.														*/
/************************************************************************/

#include <proto/dos.h>

#include <stdlib.h>
#include <string.h>
#include <m68881.h>
#include <math.h>

#include "crinkle.h"

static Strip *make_strip( int );
static void free_strip( Strip *);
static Strip *double_strip( Strip *);
static Strip *set_strip( int, Height );
static void x_update( int, double, double, Strip *, Strip *, Strip * );
static void p_update( int, double, double, Strip *, Strip *, Strip * );
static void t_update( int, double, double, Strip *, Strip *, Strip * );
static void v_update( int, double, double, Strip *, Strip *, Strip * );
static void vside_update( int, double, double, Strip * );
static void hside_update( int, double, double, Strip *, Strip *, Strip * );

static Strip *make_strip( int level )
{
	Strip	*p;
	int		points;

	p = (Strip *) malloc( sizeof(Strip) );
	if( p == NULL ) {
		PutStr( "make_strip: malloc failed\n" );
		exit( 1 );
	}
	p->level	= level;
	points		= (1 << level) + 1;
	p->d		= (Height *) malloc( points * sizeof(Height) );
	if( p->d == NULL ) {
		PutStr( "make_strip: malloc failed\n" );
		exit( 1 );
	}
	return p;
}

static void free_strip( Strip *p )
{
	if( p->d ) {
		free( p->d );
		p->d = NULL;
	}
	free( p );
}

static Strip *double_strip( Strip *s )
{
	Strip	*p;
	Height	*a, *b;
	int		i;

	p = make_strip( s->level + 1 );
	a = s->d;
	b = p->d;
	for( i = 0; i < ( 1 << s->level ); i++ ) {
		*b++	= *a++;
		*b		= 0.0;
		b++;
	}
	*b = *a;
	return p;
}

static Strip *set_strip( int level, Height value )
{
	int		i;
	Strip	*s;
	Height	*h;

	s = make_strip( level );
	h = s->d;
	for( i = 0; i < ( ( 1 << level ) + 1 ); i++ ) {
		*h++ = value;
	}
	return s;
}

/* -------------------------------------------------------------------- */
/* Initialise the fold structures.										*/
/* As  everything  else reads the parameters from their fold structs we	*/
/* need to set these here,												*/
/*																		*/
/*	p		is the parameter struct common to all update levels.		*/
/*	levels	is the number of levels of recursion below this one.		*/
/*	Number of points = 2^levels+1										*/
/*	stop	is the number of levels that are generated as random		*/
/*			offsets from a constant rather than from an average.		*/
/*	fractal_start,														*/
/*			if true we start in the middle of a mountain range			*/
/*			if false we build up mountains from the start height.		*/
/*	length	is the length of the side of the square at this level.		*/
/*			N.B this means the update square NOT the width of the		*/
/*			fractal.													*/
/*	len		gets smaller as the level increases.						*/
/*	start,	the starting height for a non-fractal start.				*/
/* -------------------------------------------------------------------- */

Fold *make_fold( Parm *param, int levels, int stop, Length length )
{
	Fold	*p;
	Length	scale, midscale;
	double	root2;
	int i;

  if( ( levels < stop ) || ( stop < 0 ) ) {
		Printf(
			"make_fold: invalid parameters\n"
			"make_fold: levels = %ld , stop = %ld \n",
			levels,
			stop
		);
		exit( 1 );
	}
	p = (Fold *) malloc( sizeof(Fold) );
	if( p == NULL ) {
		PutStr( "make_fold: malloc failed\n" );
		exit( 1 );
	}
	root2		= sqrt( 2.0 );
	scale		= pow( length, 2.0 * param->fdim );
	midscale	= pow( length * root2, 2.0 * param->fdim );
	p->level	= levels;
	p->stop		= stop;
	p->state	= START;
	p->save		= NULL;
	p->p		= param;
	p->scale	= scale;
	p->midscale	= midscale;
	for( i = 0; i < NSTRIP; i++ ) {
		p->s[i] = NULL;
	}
	if( levels > stop ) {
		p->next = make_fold( param, levels - 1, stop, 2.0 * length );
	}
	else {
		p->next = NULL;
	}
	return p;
}

void free_fold( Fold *f )
{
	int	i;
	for ( i = 0; i < NSTRIP; i++ ) {
		if( f->s[i] != NULL ) {
			free_strip( f->s[i] );
			f->s[i] = NULL;
		}
	}
	free( f );
}

Strip *next_strip( Fold *fold )
{
	Strip	*result = NULL;
	Strip	*tmp;
	Strip	**t;
	int		i, count, iter;

	count = ( 1 << fold->level ) + 1;
	if( fold->level == fold->stop ) {

		/* ------------------------------------------------------------ */
		/* generate values from scratch									*/
		/* ------------------------------------------------------------ */

		result = make_strip( fold->stop );
		for( i = 0; i < count; i++ ) {
			result->d[i] = fold->p->mean + fold->scale * gaussian();
		}
	}
	else {

		/* ------------------------------------------------------------ */
		/* There are two types of strip,								*/
		/*																*/
		/* A strips -	generated by the lower recursion layers.		*/
		/* 				these contain the corner points and half the	*/
		/*				side points										*/
		/* B strips -	added by this layer, this contains the mid		*/
		/*				points and half the side points.				*/
		/*																*/
		/* The various update routines test for NULL pointer arguments	*/
		/* so that this routine will not fail while filling the			*/
		/* pipeline.													*/
		/* ------------------------------------------------------------ */

		while( result == NULL ) {

			/* -------------------------------------------------------- */
			/* iterate													*/
			/* -------------------------------------------------------- */

			switch( fold->state ) {

				/* ---------------------------------------------------- */
				/* perform an update. return first result				*/
				/* ---------------------------------------------------- */

				case START:
					t = fold->s;

					/* ------------------------------------------------ */
					/* read in a new A strip at the start of the		*/
					/* pipeline											*/
					/* ------------------------------------------------ */

					tmp		= next_strip( fold->next );
					t[0]	= double_strip( tmp );
					free_strip( tmp );

					/* ------------------------------------------------ */
					/* make the new B strip								*/
					/* ------------------------------------------------ */

					t[1] = set_strip( fold->level, 0.0 );
					if( ! t[2] ) {

						/* -------------------------------------------- */
						/* we want to have an A B A pattern of strips	*/
						/* at the start of the pipeline.				*/
						/* force this when starting the pipe			*/
						/* -------------------------------------------- */

						t[2]	= t[0];
						tmp		= next_strip( fold->next );
						t[0]	= double_strip( tmp );
						free_strip( tmp );
					}

					/* ------------------------------------------------ */
					/* create the mid point								*/
					/* t := A B A										*/
					/* ------------------------------------------------ */

					x_update( count, fold->midscale, 0.0, t[0], t[1], t[2] );

					if( fold->p->rg1 ) {

						/* -------------------------------------------- */
						/* first possible regeneration step				*/
						/* use the midpoints to regenerate the corner	*/
						/* values increment t by 2 so we still have and	*/
						/* A B A pattern								*/
						/* -------------------------------------------- */

						v_update( count, fold->midscale, fold->p->midmix, t[1], t[2], t[3] );
						t += 2;
					}

					/* ------------------------------------------------ */
					/* fill in the edge points							*/
					/* increment t by 2 to preserve the A B A pattern	*/
					/* ------------------------------------------------ */

					if( fold->p->cross ) {
						t_update( count, fold->scale, 0.0, t[0], t[1], t[2] );
						p_update( count, fold->scale, 0.0, t[1], t[2], t[3] );
						t += 2;
					}
					else {
						hside_update( count, fold->scale, 0.0, t[0], t[1], t[2] );
						vside_update( count, fold->scale, 0.0, t[2] );
						t += 2;
					}

					if(fold->p->rg2) {

						/* -------------------------------------------- */
						/* second regeneration step update midpoint		*/
						/* from the new edge values						*/
						/*--------------------------------------------- */

						if( fold->p->cross ) {
							p_update( count, fold->scale, fold->p->mix, t[0], t[1], t[2] );
						}
						else {
							vside_update( count, fold->scale, fold->p->mix, t[1] );
						}
					}

					/* ------------------------------------------------ */
					/* increment t by 1									*/
					/* this gives a B A B pattern to regen - 3			*/
					/* if regen 3 is not being used it leaves t			*/
					/* pointing to the 2 new result strips				*/
					/* ------------------------------------------------ */

					t++;

					if( fold->p->rg3 ) {

						/* -------------------------------------------- */
						/* final regenration step						*/
						/* regenerate the corner points from the new	*/
						/* edge values this needs a B A B pattern leave	*/
						/* t pointing to the 2 new result strips		*/
						/*												*/
						/* this has to be a t_update					*/
						/* -------------------------------------------- */

						t_update( count, fold->scale, fold->p->mix, t[0], t[1], t[2] );
						t++;
					}
					result		= t[1];
					fold->save	= t[0];
					t[0]		=
					t[1]		= NULL;
					fold->state	= STORE;
					break;

				/* ---------------------------------------------------- */
				/* return second value from previous update.			*/
				/* ---------------------------------------------------- */

				case STORE:
					result		= fold->save;
					fold->save	= NULL;
					for( i = NSTRIP - 1; i > 1; i-- ) {
						fold->s[i] = fold->s[i-2];
					}
					fold->s[0]	=
					fold->s[1]	= NULL;
					fold->state	= START;
					break;

				default:
					Printf(
						"next_strip: invalid state level %ld state %ld\n",
						fold->level,
						fold->state
					);
					exit(3);
			}
		}
 	}
	iter = fold->level - fold->stop;
	if( fold->p->force_front > iter ) {
		result->d[0] = fold->p->forceval;
	}
	if( fold->p->force_back > iter ) {
		result->d[count-1] = fold->p->forceval;
	}
	return result;
}

static void x_update( int count, double scale, double mix, Strip *a, Strip *b, Strip *c )
{
	int		i;
	double	w;
	Height	*mp, *lp, *rp;

	/* ---------------------------------------------------------------- */
	/* don't run unless we have all the parameters						*/
	/* ---------------------------------------------------------------- */

	if( !a || !c ) return;
	if( !b ) {
		PutStr( "x_update: attempt to update NULL strip\n" );
		exit( 1 );
	}

	w	= ( 1.0 - mix ) / 4.0;
	mp	= b->d;
	lp	= a->d;
	rp	= c->d;

	if( mix <= 0.0 ) {

		/* ------------------------------------------------------------ */
		/* random offset to average of new points						*/
		/* ------------------------------------------------------------ */

		for ( i = 0; i < count - 2; i += 2 ) {
			mp[1] = 0.25 * ( lp[0] + rp[0] + lp[2] + rp[2] ) + scale * gaussian();
			mp += 2;
			lp += 2;
			rp += 2;
		}
	}
	else if ( mix >= 1.0 ) {

		/* ------------------------------------------------------------ */
		/* random offset to old value									*/
		/* ------------------------------------------------------------ */

		for ( i = 0; i < count - 2; i += 2 ) {
			mp[1] += scale * gaussian();
			mp += 2;
			lp += 2;
			rp += 2;
		}
	}
	else {

		/* ------------------------------------------------------------ */
		/* mixed update													*/
		/* ------------------------------------------------------------ */

		for ( i = 0; i < count - 2; i += 2 ) {
			mp[1] = mix * mp[1] + w * ( lp[0] + rp[0] + lp[2] + rp[2] ) + scale * gaussian();
			mp += 2;
			lp += 2;
			rp += 2;
		}
	}
}    

static void p_update( int count, double scale, double mix, Strip *a, Strip *b, Strip *c )
{
	int		i;
	double	w;
	Height	*mp, *lp, *rp;

	/* ---------------------------------------------------------------- */
	/* don't run if we have no parameters								*/
	/* ---------------------------------------------------------------- */

	if( !a || !b ) return;

	/* ---------------------------------------------------------------- */
	/* if c is missing we can do a vside update instead					*/
	/* should really be a sideways t but what the heck we only need		*/
	/* this at the start												*/
	/* ---------------------------------------------------------------- */

	if( !c ) {
		vside_update( count, scale, mix, b );
		return;
	}

	w	= ( 1.0 - mix ) / 4.0;
	mp	= b->d;
	lp	= a->d;
	rp	= c->d;

	if( mix <= 0.0 ) {

		/* ------------------------------------------------------------ */
		/* random offset to average of new points						*/
		/* ------------------------------------------------------------ */

		for( i = 0; i < count - 2; i += 2 ) {
			mp[1] = 0.25 * ( lp[1] + rp[1] + mp[0] + mp[2] ) + scale * gaussian();
			mp += 2;
			lp += 2;
			rp += 2;
		}
	}
	else if ( mix >= 1.0 ) {

		/* ------------------------------------------------------------ */
		/* random offset to old values									*/
		/* ------------------------------------------------------------ */

		for ( i = 0; i < count - 2; i += 2 ) {
			mp[1] += scale * gaussian();
			mp += 2;
			lp += 2;
			rp += 2;
		}
	}
	else {

		/* ------------------------------------------------------------ */
		/* mixed update													*/
		/* ------------------------------------------------------------ */

		for ( i = 0; i < count - 2; i += 2 ) {
			mp[1] = mix * mp[1] + w * ( lp[1] + rp[1] + mp[0] + mp[2] ) + scale * gaussian();
			mp += 2;
			lp += 2;
			rp += 2;
		}
	}
}    

static void t_update( int count, double scale, double mix, Strip *a, Strip *b, Strip *c )
{
	int		i;
	double	w, we;
	Height	*mp, *lp, *rp;

	/* ---------------------------------------------------------------- */
	/* don't run unless we have all the parameters						*/
	/* ---------------------------------------------------------------- */

	if( !a || !c ) return;
	if( !b ) {
		PutStr( "t_update: attempt to update NULL strip\n" );
		exit( 1 );
	}

	w	= ( 1.0 - mix ) / 4.0;
	we	= ( 1.0 - mix ) / 3.0;
	mp	= b->d;
	lp	= a->d;
	rp	= c->d;

	if( mix <= 0.0 ) {

		/* ------------------------------------------------------------ */
		/* random offset to average of new points						*/
		/* ------------------------------------------------------------ */

		mp[0] = ( 1.0 / 3.0 ) * ( lp[0] + rp[0] + mp[1] ) + scale * gaussian();
		mp++;
		lp++;
		rp++;
		for( i = 1; i < count - 3; i += 2 ) {
			mp[1] = 0.25 * ( lp[1] + rp[1] + mp[0] + mp[2] ) + scale * gaussian();
			mp += 2;
			lp += 2;
			rp += 2;
		}
		mp[1] = ( 1.0 / 3.0 ) * ( lp[1] + rp[1] + mp[0] ) + scale * gaussian();
	}
	else if ( mix >= 1.0 ) {

		/* ------------------------------------------------------------ */
		/* random offset to old values									*/
		/* ------------------------------------------------------------ */

		for( i = 0; i < count - 2; i += 2 ) {
			mp[0] += scale * gaussian();
			mp += 2;
			lp += 2;
			rp += 2;
		}
	}
	else {

		/* ------------------------------------------------------------ */
		/* mixed update													*/
		/* ------------------------------------------------------------ */

		mp[0] = mix * mp[0] + we * ( lp[0] + rp[0] + mp[1] ) + scale * gaussian();
		mp++;
		lp++;
		rp++;
		for ( i = 1; i < count - 3; i += 2 ) {
			mp[1] = mix * mp[1] + w * ( lp[1] + rp[1] + mp[0] + mp[2] ) + scale * gaussian();
			mp += 2;
			lp += 2;
			rp += 2;
		}
		mp[1] = mix * mp[1] + we * ( lp[1] + rp[1] + mp[0] ) + scale * gaussian();
	}
}    

static void v_update( int count, double scale, double mix, Strip *a, Strip *b, Strip *c )
{
	int i;
	double w, we;
	Height *mp, *lp, *rp;

	/* ---------------------------------------------------------------- */
	/* don't run unless we have all the parameters						*/
	/* ---------------------------------------------------------------- */

	if( !a || !c ) return;
	if( !b ) {
		PutStr( "v_update: attempt to update NULL strip\n" );
		exit( 1 );
	}

	w	= ( 1.0 - mix ) / 4.0;
	we	= ( 1.0 - mix ) / 2.0;
	mp	= b->d;
	lp	= a->d;
	rp	= c->d;

	if ( mix <= 0.0 ) {

		/* ------------------------------------------------------------ */
		/* random offset of average of new points						*/
		/* ------------------------------------------------------------ */

		mp[0] = 0.5 * ( lp[1] + rp[1] ) + scale * gaussian();
		mp++;
		lp++;
		rp++;
		for( i = 1; i < count - 3; i += 2 ) {
			mp[1] = 0.25 * ( lp[0] + rp[0] + lp[2] + rp[2] ) + scale * gaussian();
			mp += 2;
			lp += 2;
			rp += 2;
		}
		mp[1] = 0.5 * ( lp[0] + rp[0] ) + scale * gaussian();
	}
	else if( mix >= 1.0 ) {

		/* ------------------------------------------------------------ */
		/* random offset to old values									*/
		/* ------------------------------------------------------------ */

		for( i = 0; i < count - 2; i += 2 ) {
			mp[0] += scale * gaussian();
			mp += 2;
			lp += 2;
			rp += 2;
		}
	}
	else {

		/* ------------------------------------------------------------ */
		/* mixed update													*/
		/* ------------------------------------------------------------ */

		mp[0] = mix * mp[0] + we * ( lp[1] + rp[1] ) + scale * gaussian();
		mp++;
		lp++;
		rp++;
		for( i = 1; i < count - 3; i += 2 ) {
			mp[1] = mix * mp[1] + w * ( lp[0] + rp[0] + lp[2] + rp[2] ) + scale * gaussian();
			mp += 2;
			lp += 2;
			rp += 2;
		}
		mp[1] = mix * mp[1] + we * ( lp[0] + rp[0] ) + scale * gaussian();
	}
}    

static void vside_update( int count, double scale, double mix, Strip *a )
{
	int		i;
	double	w;
	Height	*mp;

	/* ---------------------------------------------------------------- */
	/* don't run unless we have all the parameters						*/
	/* ---------------------------------------------------------------- */

	if( !a ) return;


	w	= ( 1.0 - mix ) / 2.0;
	mp	= a->d;

	if ( mix <= 0.0 ) {

		/* ------------------------------------------------------------ */
	    /* random offset to average of new points						*/
		/* ------------------------------------------------------------ */

	    for ( i = 0; i < count - 2; i += 2 ) {
			mp[1] = 0.5 * ( mp[0] + mp[2] ) + scale * gaussian();
			mp += 2;
		}
	}
	else if ( mix >= 1.0 ) {

		/* ------------------------------------------------------------ */
		/* random offset to old values									*/
		/* ------------------------------------------------------------ */

		for( i = 0; i < count - 2; i += 2 ) {
			mp[1] += scale * gaussian();
			mp += 2;
		}
	}
	else {

		/* ------------------------------------------------------------ */
		/* mixed update													*/
		/* ------------------------------------------------------------ */

		for ( i = 0; i < count - 2; i += 2 ) {
			mp[1] = mix * mp[1] + w * ( mp[0] + mp[2] ) + scale * gaussian();
			mp += 2;
		}
	}
}    


static void hside_update( int count, double scale, double mix, Strip *a, Strip *b, Strip *c )
{
	int		i;
	double	w;
	Height	*mp, *lp, *rp;

	/* ---------------------------------------------------------------- */
	/* don't run unless we have all the parameters						*/
	/* ---------------------------------------------------------------- */

	if ( !a || !c ) return;
	if ( !b ) {
		PutStr( "x_update: attempt to update NULL strip\n" );
		exit( 1 );
	}

	w	= ( 1.0 - mix ) / 2.0;
	mp	= b->d;
	lp	= a->d;
	rp	= c->d;

	if ( mix <= 0.0 ) {

		/* ------------------------------------------------------------ */
		/* random offset to average of new points						*/
		/* ------------------------------------------------------------ */

		for ( i = 0; i < count; i += 2 ) {
			mp[0] = 0.5 * ( lp[0] + rp[0] ) + scale * gaussian();
			mp += 2;
			lp += 2;
			rp += 2;
		}
	}
	else if ( mix >= 1.0 ) {

		/* ------------------------------------------------------------ */
		/* random offset to old points									*/
		/* ------------------------------------------------------------ */

		for ( i = 0; i < count; i += 2 ) {
			mp[0] += scale * gaussian();
			mp += 2;
			lp += 2;
			rp += 2;
		}
	}
	else {

		/* ------------------------------------------------------------ */
		/* mixed update													*/
		/* ------------------------------------------------------------ */

		for ( i = 0; i < count; i += 2 ) {
			mp[0] = mix * mp[0] + w * ( lp[0] + rp[0] ) + scale * gaussian();
			mp += 2;
			lp += 2;
			rp += 2;
		}
	}
}    
