
/* Standard compiler headers */

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

/* AXP header included with the IPAS toolkit */
#include "axp.h"

/* CATAIL header file */
#include "catail.h"

/* Defines for the dialog box variables */

#define DENSITY 1
#define FLOWER  2
#define COURSE  3
#define SEED    4

/* The dialog box for the cat tails settings
   This sets up the whole dialog box with the layout being done
   by 3D Studio. See the IPAS Toolkit guide for the syntax. */

DlgEntry cdialog[]={
	0,"TITLE=\"3D Studio Special Effects\"",
	0,"TITLE=\"Keith A. Seifert\"",
	0,"TITLE=\"Cat Tail Generator\"",
	0,"TITLE=\"Version 1.0\"",
	0,"TITLE=\"\"",
	DENSITY,"LFLOAT=\"Cat Tail Density:\",0.001,1.0,6",
	COURSE,"LFLOAT=\"Cat Tail Size:\",0.01,1.0,6",
	FLOWER,"ON-OFF=\"Flower:\"",
	SEED,"LINT=\"Seed:\",0,60000,7",
	0,"TITLE=\"\"",
	0,NULL
};

/* Each IPAS process needs a unique version number */

#define VERSION 0xAB01

/* The state structure contains all the variables which will be
   saved and restored by 3D Studio */ 

typedef struct {
	ulong version;
	float density,size;
	int flower;
   int seed;
} State;

/* The default settings for the state structure */

static State init_state = { VERSION,0.1,0.5,0,23456 };
static State state = { VERSION,0.1,0.5,0,23456 };


/* The standard function for setting the variables from the
   dialog box. All AXP routines need this function */

void ClientSetStateVar(int id, void *ptr) {
	OVL o;
	ulong *ul;

	ul=(ulong *)ptr;
	o.ul = *ul;
	switch(id) {
		case DENSITY:
         state.density = o.f;
         break;
		case COURSE:
         state.size = o.f;
         break;
		case FLOWER:
         state.flower = o.i;
         break;
		case SEED:
         state.seed = o.i;
         break;
	}
}


/* The standard function for getting the variables for the
   dialog box. All AXP routines need this function */

ulong ClientGetStateVar(int id) {
	OVL o;
	switch(id) {
		case DENSITY:
         o.f = state.density;
         break;
		case COURSE:
         o.f = state.size;
         break;
		case FLOWER:
         o.i = state.flower;
         break;
		case SEED:
         o.i = state.seed;
         break;
   }
	return(o.ul);
}


/* The standard function for setting the variable size.
   All AXP routines need this function */

int ClientVarSize(int id) {
	return(1);
}


/* The standard function for getting the size of the state
   structure. All AXP routines need this function */

char  *ClientGetState(int *size) {
	*size = sizeof(State);
	return((char *)&state);
}


/* The standard function for resetting the state structure
   with the default setup. All AXP routines need this function */

void ClientResetState() {	
	state = init_state;	
}


/* The standard function for starting the external process.
   At this time all the global setup should be done. At this
   point the external process begins to control the flow of
   the process. All AXP routines need this function */

void ClientStartup(EXPbuf *buf) {
   int seed;
   time_t t;
   struct tm *tim;

      /* Setup the random number generator */

   if( !state.seed ) {
      time( &t );
      tim = gmtime( &t );
      seed = tim->tm_sec;
   }
   else
      seed = state.seed;
   srand( seed );

      /* Get the current 3D Studio version */

   version3d = studio_version();

      /* Get the number vertices in the current object */

	overts = buf->data.object.verts;

      /* Get the current frame */

   if( buf->data.object.curfield & 1 )
	   frame = (buf->data.object.curfield-1) / 2;
   else
	   frame = (buf->data.object.curfield) / 2;

      /* Initialize global variables */

   obj.vt = NULL;
   obj.fc = NULL;
	first = 1;
	ix = 0;

      /* Setup the next event */

	buf->data.vert.vertex = ix;
	buf->opcode = EXP_GET_VERTEX;
	buf->usercode = 0x0200;
	buf->status = 1;
}

/* The standard function for the whole external process.
   The flow between 3D Studio and the external process all
   happens within this structure.
   All AXP routines need this function */

void ClientUserCode(EXPbuf *buf) {
   int i;

   /* The usercode value controls what happens next. It is set
      by the external process on the previous call to
      3D studio */

	switch(buf->usercode) {
		case 0x0200:

         /* For each vertex find the min and max at each axis */

			if( first ) {
				minx = maxx = buf->data.vert.x;
				miny = maxy = buf->data.vert.y;
				minz = maxz = buf->data.vert.z;
				first = 0;
			}
			else {
				if( buf->data.vert.x < minx )
					minx = buf->data.vert.x;
				else if( buf->data.vert.x > maxx )
					maxx = buf->data.vert.x;
				if( buf->data.vert.y < miny )
					miny = buf->data.vert.y;
				else if( buf->data.vert.y > maxy )
					maxy = buf->data.vert.y;
				if( buf->data.vert.z < minz )
					minz = buf->data.vert.z;
				else if( buf->data.vert.z > maxz )
					maxz = buf->data.vert.z;
			}

         /* Increament vertex count */

			ix++;
			if( ix < overts ) {

            /* Get next vertex */

				buf->data.vert.vertex = ix;
				buf->opcode = EXP_GET_VERTEX;
				buf->usercode = 0x0200;
				buf->status = 1;
				break;
			}
			if( buf->status == 0 ) {
            /* Some kind of problem in 3D Studio */

				terminate:
				buf->opcode=buf->usercode = EXP_TERMINATE;
				break;
			}

         /* Find axis range */

			dx = maxx - minx;
			dy = maxy - miny;
			dz = maxz - minz;

         /* Calculate number of cat tails */

         nweeds = 500 * state.density + 1;
         if( state.flower ) {
            nentv = 77;
            nentf = 110;
         }
         else {
            nentv = 67;
            nentf = 94;
         }

         /* Reset total if to many */

         while( nentf * nweeds > 65500 )
            nweeds--;

         /* Set cat tail size */

         weedwd = state.size * 0.002;

         /* Now get faces */

			buf->opcode = EXP_GET_FACE;
			buf->usercode = 0x0210;
			buf->data.face.face = 0;
			break;

		case 0x0210:

         /* Pick up material to apply later */

			material = buf->data.face.material;

         /* Now go create Cat tails */

         if( BuildCatTails() ) {

            /* Error returned */

            ClientTerminate();
	   	   buf->opcode = buf->usercode = EXP_TERMINATE;
		      buf->status = 0;
            break;
         }

         /* Ready Object to be created */

			buf->opcode = EXP_READY_OBJ;

         /* Use single mode if version 2.0
            Use burst mode if version 2.01 or higher */

         if( version3d > 200 )
			   buf->usercode = 0x0260;
         else
			   buf->usercode = 0x0240;
			buf->status = 1;
         
			buf->data.object.verts = obj.nvertex;
			buf->data.object.faces = obj.nface;
			buf->data.object.tverts = obj.nvertex;
			break;

		case 0x0240:

         /* Single vertex mode */

			vertex = 0;
			if( buf->status == 0 )
				goto terminate;

		case 0x0245:

         /* For each new vertex send the vertex data to Studio */

			vertex_loop:
			if( vertex >= obj.nvertex )
				goto face_loop;
			buf->opcode = EXP_PUT_VERTEX;
			buf->usercode = 0x0245;
			buf->data.vert.x = obj.vt[vertex].x;
			buf->data.vert.y = obj.vt[vertex].y;
			buf->data.vert.z = obj.vt[vertex].z;
			buf->data.vert.u = obj.vt[vertex].u;
			buf->data.vert.v = obj.vt[vertex].v;
			buf->data.vert.vertex = vertex++;
			break;

		case 0x0250:

         /* Single face mode */

			face_loop:
			face = 0;

		case 0x0255:
			if( face >= obj.nface ) {

            /* Finished with faces now create object */

			   buf->opcode = EXP_CREATE_OBJ;
            buf->usercode = EXP_TERMINATE;
			   buf->status = 1;
            ClientTerminate();
			}
         else {

            /* For each face send Studio the face data */

			   buf->opcode = EXP_PUT_FACE;
			   buf->data.face.sm_group = obj.fc[face].sm_group;
			   buf->data.face.material = material;
			   buf->data.face.a = obj.fc[face].a;
			   buf->data.face.b = obj.fc[face].b;
			   buf->data.face.c = obj.fc[face].c;
		      buf->data.face.face = face++;
			   buf->data.face.flags =
               FC_CALINE | FC_ABLINE | FC_BCLINE;

			   buf->usercode = 0x0255;
         }
         break;

		case 0x0260:

         /* Burst Mode sends the whole vertex list */

			buf->opcode = EXP_PUT_VLIST;
			buf->usercode = 0x0265;
			buf->data.vlist.start = 0;
			buf->data.vlist.count = obj.nvertex;
			buf->data.vlist.data = (_far VData *)obj.vt;
			buf->status = 1;
			break;

		case 0x0265:

         /* Burst mode sends the whole face list */

         for( i=0;i<obj.nface;i++ ) {
            obj.fc[i].material = material;
            obj.fc[i].flags = FC_CALINE | FC_ABLINE | FC_BCLINE;
         }

			buf->opcode = EXP_PUT_FLIST;
			buf->usercode = 0x0270;
			buf->data.flist.start = 0;
			buf->data.flist.count = obj.nface;
			buf->data.flist.data = (_far FData *)obj.fc;
			buf->status = 1;
			break;

		case 0x0270:

         /* Create the object in Studio */

			buf->data.object.flags = (uchar)0;
			buf->opcode = EXP_CREATE_OBJ;
         buf->usercode = EXP_TERMINATE;
			buf->status = 1;
			break;

		default:
		   error("Invalid user opcode",NULL,NULL,NULL,NULL);
	   	buf->opcode = buf->usercode = EXP_TERMINATE;
		   buf->status = 0;
	}
}


/* The standard function for terminating. The external function
   should free all allocated data here. 3D Studio will call this
   function when unloading the process. Version 3.0 will not
   unload the process until 3D Studio is exited so memory
   may be need to be freed prior to unloading.
   All AXP routines need this function */

void ClientTerminate(void) {
   if( obj.vt ) { free( obj.vt ); obj.vt = NULL; }
   if( obj.fc ) { free( obj.fc ); obj.fc = NULL; }
}


/* The standard function for returning the number lines in the
   dialog box. All AXP routines need this function */

DlgEntry *ClientDialog(int n) {	
	return(&cdialog[n]); 
}


/* The standard function for returning the name of process.
   All AXP routines need this function */

char *ClientName(void) {
	return("CATAIL.AXP");
}


/* The external process code itself */

/* In this case only one additional function is used
   BuildCatTails creates all the cat tails and stores
   them in a memory structure to be returned to 3D Studio */

int BuildCatTails(void) {
   int i,j,k,cvert,cface,bvert,bbase,mbase;
   double tailst,tailend,bloc1,bloc2,bht1,bht2,locoff,bnd,wdmod;
   double srol,srx,srz,rdis,cht,wdis,mmoff;

      /* Find number of faces and vertices */

   obj.nvertex = nentv * nweeds;
   obj.nface = nentf * nweeds;

      /* Allocate data space */

   obj.vt = (VData*)malloc( obj.nvertex * sizeof( VData ) );
   obj.fc = (FData*)malloc( obj.nface * sizeof( FData ) );

      /* Return on memory error */

   if( !obj.vt || !obj.fc )
      return -1;

      /* Wind direction */

   srol = drand() * RD_360;
   srx = cos(srol);
   srz = sin(srol);

      /* Setup full motion over 48 frames */

   wdis = (double)frame;
   while( wdis > 47.0 )
      wdis -= 48.0;
   mbase = (int)wdis;
   if( mbase < 13 )
      mmoff = (double)mbase / 12.0;
   else if( mbase < 25 )
      mmoff = 1.0 - (double)(mbase - 12) / 12.0;
   else if( mbase < 37 )
      mmoff = (double)(24 - mbase) / 12.0;
   else
      mmoff = -1.0 + (double)(mbase - 36) / 12.0;

      /* Init variables */

   bvert = cvert = cface = 0;

      /* For each cat tail create object */

   for( i=0;i<nweeds;i++ ) {

      /* Locate cat tail */

		px = drand() * dx + minx;
		pz = drand() * dz + minz;

      /* Setup height variables */

      hper = (drand() * 0.5 + 0.5) * dy;
      tailst = (drand() * 0.1 + 0.7) * hper;
      tailend = (drand() * 0.05 + 0.15) * hper + tailst;
      if( tailend > hper )
         tailend = 0.97 * hper;
      wdis = (drand() * 0.04 + 0.01) * hper;

      /* Setup leaf variables */

		bloc1 = drand() * RD_360;
      bht1 = (drand() * 0.25 + 0.7) * hper;

		bloc2 = bloc1 + RD_180 + (drand() * RD_45 - RD_22);
      if( bloc2 > RD_360 )
         bloc2 -= RD_360;
      bht2 = (drand() * 0.25 + 0.7) * hper;

      curwd = weedwd * hper + weedwd;
      segrg = 0.1667 * (drand() * 0.06 + 0.97);

      /* Calculate stalk vertices */

      for( j=0;j<8;j++,cvert++ ) {
         obj.vt[cvert].x = px + xpos[j] * curwd;
         obj.vt[cvert].z = pz + ypos[j] * curwd;
         obj.vt[cvert].y = maxy;
         obj.vt[cvert].u = 0.075 * j;
         obj.vt[cvert].v = 0.0;
      }
      rdis = (tailst / hper) * (tailst / hper) * wdis * mmoff;
      for( j=0;j<8;j++,cvert++ ) {
         obj.vt[cvert].x = px + xpos[j] * curwd + srx * rdis;
         obj.vt[cvert].z = pz + ypos[j] * curwd + srz * rdis;
         obj.vt[cvert].y = maxy - tailst;
         obj.vt[cvert].u = 0.075 * j;
         obj.vt[cvert].v = 0.76;
      }
      cht = ((tailend-tailst) * 0.08 + tailst);
      rdis = (cht / hper) * (cht / hper) * wdis * mmoff;
      for( j=0;j<8;j++,cvert++ ) {
         obj.vt[cvert].x = px + xpos[j]*curwd*4.5 + srx*rdis;
         obj.vt[cvert].z = pz + ypos[j]*curwd*4.5 + srz*rdis;
         obj.vt[cvert].y = maxy - cht;
         obj.vt[cvert].u = 0.075 * j;
         obj.vt[cvert].v = 0.77;
      }
      cht = (tailend - (tailend-tailst) * 0.08);
      rdis = (cht / hper) * (cht / hper) * wdis * mmoff;
      for( j=0;j<8;j++,cvert++ ) {
         obj.vt[cvert].x = px + xpos[j]*curwd*4.5 + srx*rdis;
         obj.vt[cvert].z = pz + ypos[j]*curwd*4.5 + srz*rdis;
         obj.vt[cvert].y = maxy - cht;
         obj.vt[cvert].u = 0.075 * j;
         obj.vt[cvert].v = 0.83;
      }
      rdis = (tailend / hper) * (tailend / hper) * wdis * mmoff;
      for( j=0;j<8;j++,cvert++ ) {
         obj.vt[cvert].x = px + xpos[j] * curwd + srx * rdis;
         obj.vt[cvert].z = pz + ypos[j] * curwd + srz * rdis;
         obj.vt[cvert].y = maxy - tailend;
         obj.vt[cvert].u = 0.075 * j;
         obj.vt[cvert].v = 0.84;
      }
      obj.vt[cvert].x = px + srx * wdis * mmoff;
      obj.vt[cvert].z = pz + srz * wdis * mmoff;
      obj.vt[cvert].y = maxy - hper;
      obj.vt[cvert].u = 0.3;
      obj.vt[cvert].v = 0.9;
      cvert++;
      if( state.flower ) {

         /* Add the flower of requested */

         cht = ((hper-tailend) * 0.1 + tailend);
         rdis = (cht / hper) * (cht / hper) * wdis * mmoff;
         obj.vt[cvert].x = px + srx * rdis;
         obj.vt[cvert].z = pz + srz * rdis;
         obj.vt[cvert].y = maxy - cht;
         obj.vt[cvert].u = 0.3;
         obj.vt[cvert].v = 0.9;
         cvert++;
         cht = ((hper-tailend) * 0.3 + tailend);
         rdis = (cht / hper) * (cht / hper) * wdis * mmoff;
         for( j=0;j<8;j++,cvert++ ) {
            obj.vt[cvert].x = px + xpos[j]*curwd*3.5 + srx*rdis;
            obj.vt[cvert].z = pz + ypos[j]*curwd*3.5 + srz*rdis;
            obj.vt[cvert].y = maxy - cht;
            obj.vt[cvert].u = 0.075 * j;
            obj.vt[cvert].v = 0.91;
         }
         cht = hper * 1.03;
         rdis = (cht / hper) * (cht / hper) * wdis * mmoff;
         obj.vt[cvert].x = px + srx * rdis;
         obj.vt[cvert].z = pz + srz * rdis;
         obj.vt[cvert].y = maxy - cht;
         if( obj.vt[cvert].y < miny )
            obj.vt[cvert].y = miny;
         obj.vt[cvert].u = 0.3;
         obj.vt[cvert].v = 1.0;
         cvert++;
      }

      /* Calculate first leaf variables */

      cht = (mmoff * -0.025) + 1.0;

      xdir = (float)cos(bloc1);
      xfac = xdir * curwd * 2.0;
      zdir = (float)sin(bloc1);
      zfac = zdir * curwd * 2.0;
      position = bloc1 + RD_90;
      if( position > RD_360 )
         position -= RD_360;
      wdmod = 1.2 + drand();
      xoff = (float)(cos(position) * curwd * wdmod);
      zoff = (float)(sin(position) * curwd * wdmod);
      bnd = (drand() * 0.13 + 0.05) * bht1;
      rdis = (drand() * 0.35) * bnd;

      /* Calculate first leaf vertices */

      for( j=0;j<6;j++,cvert++ ) {
         locoff = ((double)j / 6.0) * ((double)j / 6.0);
         obj.vt[cvert].x = px + xoff + xfac + xdir * locoff *
            bnd + srx * mmoff * locoff * rdis;
         obj.vt[cvert].z = pz + zoff + zfac + zdir * locoff *
            bnd + srz * mmoff * locoff * rdis;
         obj.vt[cvert].y = maxy - (bht1*segrg*j*cht);
         obj.vt[cvert].u = 0.6;
         obj.vt[cvert].v = bht1 * segrg * j;
         cvert++;
         obj.vt[cvert].x = px - xoff + xfac + xdir * locoff *
            bnd + srx * mmoff * locoff * rdis;
         obj.vt[cvert].z = pz - zoff + zfac + zdir * locoff *
            bnd + srz * mmoff * locoff * rdis;
         obj.vt[cvert].y = maxy - (bht1*segrg*j*cht);
         obj.vt[cvert].u = 0.8;
         obj.vt[cvert].v = bht1 * segrg * j;
      }
      obj.vt[cvert].x = px + xfac + xdir*bnd + srx*mmoff*rdis;
      obj.vt[cvert].z = pz + zfac + zdir*bnd + srz*mmoff*rdis;
      obj.vt[cvert].y = maxy - bht1 * cht;
      obj.vt[cvert].u = 0.7;
      obj.vt[cvert].v = 1.0;
      cvert++;

      /* Calculate second leaf variables */

      xdir = (float)cos(bloc2);
      xfac = xdir * curwd * 2.0;
      zdir = (float)sin(bloc2);
      zfac = zdir * curwd * 2.0;
      position = bloc2 + RD_90;
      if( position > RD_360 )
         position -= RD_360;
      wdmod = 1.2 + drand();
      xoff = (float)(cos(position) * curwd * wdmod);
      zoff = (float)(sin(position) * curwd * wdmod);
      bnd = (drand() * 0.13 + 0.05) * bht2;
      rdis = (drand() * 0.35) * bnd;

      /* Calculate second leaf vertices */

      for( j=0;j<6;j++,cvert++ ) {
         locoff = ((double)j / 6.0) * ((double)j / 6.0);
         obj.vt[cvert].x = px + xoff + xfac + xdir * locoff *
            bnd + srx * mmoff * locoff * rdis;
         obj.vt[cvert].z = pz + zoff + zfac + zdir * locoff *
            bnd + srz * mmoff * locoff * rdis;
         obj.vt[cvert].y = maxy - (bht2*segrg*j*cht);
         obj.vt[cvert].u = 0.6;
         obj.vt[cvert].v = bht2 * segrg * j;
         cvert++;
         obj.vt[cvert].x = px - xoff + xfac + xdir * locoff *
            bnd + srx * mmoff * locoff * rdis;
         obj.vt[cvert].z = pz - zoff + zfac + zdir * locoff *
            bnd + srz * mmoff * locoff * rdis;
         obj.vt[cvert].y = maxy - (bht2*segrg*j*cht);
         obj.vt[cvert].u = 0.8;
         obj.vt[cvert].v = bht2 * segrg * j;
      }
      obj.vt[cvert].x = px + xfac + xdir*bnd + srx*mmoff*rdis;
      obj.vt[cvert].z = pz + zfac + zdir*bnd + srz*mmoff*rdis;
      obj.vt[cvert].y = maxy - bht2 * cht;
      obj.vt[cvert].u = 0.7;
      obj.vt[cvert].v = 1.0;
      cvert++;

      /* Find the faces for the stalk */

      for( j=0;j<4;j++ ) {
         for( k=0;k<8;k++,cface++ ) {
            obj.fc[cface].a = bvert+j*8+k;
            if( k < 7 ) {
               obj.fc[cface].b = bvert+j*8+k+1;
               obj.fc[cface].c = bvert+(j+1)*8+k+1;
            }
            else {
               obj.fc[cface].b = bvert+j*8;
               obj.fc[cface].c = bvert+(j+1)*8;
            }
            if( j ) 
               obj.fc[cface].sm_group = 2;
            else
               obj.fc[cface].sm_group = 1;
            cface++;
            obj.fc[cface].a = bvert+j*8+k;
            if( k < 7 )
               obj.fc[cface].b = bvert+(j+1)*8+k+1;
            else
               obj.fc[cface].b = bvert+(j+1)*8;
            obj.fc[cface].c = bvert+(j+1)*8+k;
            if( j ) 
               obj.fc[cface].sm_group = 2;
            else
               obj.fc[cface].sm_group = 1;
         }
      }

      /* Top faces */

      for( k=0;k<8;k++,cface++ ) {
         obj.fc[cface].a = bvert+32+k;
         if( k < 7 )
            obj.fc[cface].b = bvert+32+k+1;
         else
            obj.fc[cface].b = bvert+32;
         obj.fc[cface].c = bvert+40;
         obj.fc[cface].sm_group = 1;
      }

      if( state.flower ) {

         /* Find the faces for the flower if requested */

         for( k=0;k<8;k++,cface++ ) {
            obj.fc[cface].a = bvert+42+k;
            obj.fc[cface].b = bvert+41;
            if( k < 7 )
               obj.fc[cface].c = bvert+42+k+1;
            else
               obj.fc[cface].c = bvert+42;
            obj.fc[cface].sm_group = 4;
            cface++;
            obj.fc[cface].a = bvert+42+k;
            if( k < 7 )
               obj.fc[cface].b = bvert+42+k+1;
            else
               obj.fc[cface].b = bvert+42;
            obj.fc[cface].c = bvert+50;
            obj.fc[cface].sm_group = 4;
         }
         bbase = 51;
      }
      else
         bbase = 41;

      /* Find the faces for the two leaves */

      for( j=0;j<2;j++ ) {
         for( k=0;k<5;k++,cface++ ) {
            obj.fc[cface].a = bvert+bbase+k*2;
            obj.fc[cface].b = bvert+bbase+(k+1)*2;
            obj.fc[cface].c = bvert+bbase+k*2+1;
            obj.fc[cface].sm_group = 8;
            cface++;
            obj.fc[cface].a = bvert+bbase+k*2+1;
            obj.fc[cface].b = bvert+bbase+(k+1)*2;
            obj.fc[cface].c = bvert+bbase+(k+1)*2+1;
            obj.fc[cface].sm_group = 8;
         }
         obj.fc[cface].a = bvert+bbase+10;
         obj.fc[cface].b = bvert+bbase+12;
         obj.fc[cface].c = bvert+bbase+11;
         obj.fc[cface].sm_group = 8;
         cface++;
         bbase += 13;
      }

      /* setup for next cat tail */

      bvert = cvert;
   }

   /* Finished no problems */

   return 0;
}
