/*
*
*
*	Faces.c, by Dave Johnson, 3/91.
*
*	Based on Clifford Pickover's Chernoff face routine, in his book "Computers, Pattern, Chaos,
*	and Beauty."
*
*
*	The face is specified by an array of ten byte values ranging from -127 to 127. Here's how
*	the parameters map to facial features (there's no rhyme or reason here, I just used the 
*	same set that Pickover did):
*
*		0 head eccentricity
*		1 eye eccentricity
*		2 pupil size
*		3 eyebrow slope
*		4 nose size
*		5 mouth vert. offset
*		6 eye spacing
*		7 eye size
*		8 mouth width
*		9 mouth "openness"
*
*
*
*/

/* prototypes for the Face routines */
void 		CenterRectOverPoint(Rect*, Point);
Point 		GetCenter(Rect *theRect);
int 		ScaleChar(char param, int max);
pascal void Face(Rect *rect, char params[]);

/*-------------------------------------------------------------------------
CenterRectOverPoint()         	Centers theRect over thePt...
--------------------------------------------------------------------------*/
void CenterRectOverPoint(Rect *theRect, Point thePt)
{
	/* First home theRect... */
	OffsetRect(theRect, -theRect->left, -theRect->top);
	
	/* ...then center it over thePt */
	OffsetRect(theRect, thePt.h - (theRect->right / 2), thePt.v - (theRect->bottom / 2));
}

/*-------------------------------------------------------------------------
GetCenter()         	Returns the center of theRect
--------------------------------------------------------------------------*/
Point GetCenter(Rect *theRect)
{
	Point		pt;
		
	pt.h = theRect->left + ((theRect->right - theRect->left) / 2);
	pt.v = theRect->top + ((theRect->bottom - theRect->top) / 2);
	return(pt);
}

/*-------------------------------------------------------------------------
ScaleChar()	Scales param to a range of + or - max, returns the scaled value.
			param is assumed to be between -127 and 127.
--------------------------------------------------------------------------*/
int ScaleChar(char param, int max)
{
	return ((long)param * max) / 127;
}

/*-------------------------------------------------------------------------
Face()         	Draws a Chernoff face, scaled to bounds, in the current port
--------------------------------------------------------------------------*/
pascal void
Face(Rect *bounds, char params[])
{
	Rect		rect, temprect;
	int			x1, x2, y1, y2, xunit, yunit, temp;
	Point		ctr, eyectr;
	PenState	oldpen;

	/*
	* Set up
	*/
	
	/* These are our basic units in x and y directions */
	xunit = (bounds->right - bounds->left) / 8;	
	yunit  = (bounds->bottom - bounds->top) / 8;
	
	/* The center of the face */
	ctr = GetCenter(bounds);				

	/* Adjust the pen size */
	GetPenState(&oldpen);
	PenNormal();
	x1 = xunit / 10;
	if(x1 < 1)
		x1 = 1;
	y1 = yunit / 10;
	if(y1 < 1)
		y1 = 1;
	PenSize(x1, y1);
	EraseRect(bounds);

	/*
	* Draw the Head
	*/
	
	/* Inset by one unit so there is room to squish around */
	rect = *bounds;
	InsetRect(&rect, xunit, yunit);
	
	/* Now add eccentricity (params[0], positive is taller) , and draw it */
	InsetRect(&rect, ScaleChar(params[0], xunit / 2), ScaleChar(-params[0], yunit / 2));
	FrameOval(&rect);
	
	/*
	* Draw the Eyes
	*/
	
	/* Set up the default eye rect */
	SetRect(&rect, 0, 0, (5 * xunit) / 4, (5 * yunit) / 4);
	
	/* Change the size (param[7], positive is bigger) */
	InsetRect(&rect, ScaleChar(-params[7], xunit / 4), ScaleChar(-params[7], yunit / 4));
	
	/* Add eccentricity (param[1], positive is taller) */
	InsetRect(&rect, ScaleChar(params[1], xunit / 4), ScaleChar(-params[1], yunit / 4));
	
	/* Set location (params[6] is eye spacing, positive is wider), and draw
		the eyes.  Also draw the pupils, while we're here. */
	
	temp = ScaleChar(params[6], xunit / 2);
	CenterRectOverPoint(&rect, ctr);
	temprect = rect;	/* save for other eye */
	OffsetRect(&rect, -(xunit + temp), -((2 * yunit) / 3));
	FrameOval(&rect);
	
	/* Draw the pupil, params[2] is diameter, bigger is bigger. 
		We want a minimum of a 1 pixel dot: no empty eyes */
	
	/* Convert params[2] to a positive number from 0 to 127 */
	x1 = (params[2] + 127) / 2;
	
	/* Scale in x and y directions, min of 1 */	
	y1 = ScaleChar(x1, yunit / 3);
 	if(y1 < 1) y1 = 1;
	x1 = ScaleChar(x1, xunit / 3);
 	if(x1 < 1) x1 = 1;
	
	/* Get the center of the eye rect */
	eyectr = GetCenter(&rect);
	
	/* Set up the rect and draw it */
	SetRect(&rect, 0, 0, 0, 0);
	InsetRect(&rect, -x1, -y1);
	CenterRectOverPoint(&rect, eyectr);
	PaintOval(&rect);
	
	/* Other eye... */
	rect = temprect;
	OffsetRect(&rect, xunit + temp, -((2 * yunit) / 3));
	FrameOval(&rect);

	/* Pupil... */
	eyectr = GetCenter(&rect);
	SetRect(&rect, 0, 0, 0, 0);
	InsetRect(&rect, -x1, -y1);
	CenterRectOverPoint(&rect, eyectr);
	PaintOval(&rect);
	
	/*
	* Eyebrows
	*/
	
	/* params[3] is slope, positive slopes down to the outside */
	temp = ScaleChar(params[3], yunit / 2);
	y1 = y2 = ctr.v - (yunit + (2 * yunit) / 3);
	y1 += temp;
	y2 -= temp;
	MoveTo(ctr.h - (3 * xunit) / 2, y1);
	LineTo(ctr.h - xunit / 2, y2);	
	MoveTo(ctr.h + (3 * xunit) / 2, y1);
	LineTo(ctr.h + xunit / 2, y2);
	
	/*
	* Nose
	*/
	
	/* params[4] is length, positive is bigger */
	y1 = yunit + (yunit / 3) + ScaleChar(params[4], yunit / 3);
	MoveTo(ctr.h, ctr.v - (yunit / 3));
	Line(xunit / 2, y1);
	Line(-xunit, 0);
	
	/*
	* Mouth
	*/
	
	/* params[8] is width, positive is wider */
	temp = ScaleChar(params[8], xunit / 2);
	y1 = ctr.v + (2 * yunit);			/* the neutral position, vertically */
	x1 = xunit + temp;					/* the horizontal offset from center */
	
	/* Get the points for the lips */
	temp = ScaleChar(params[5], yunit / 3);	/* offset for first lip */
	
	/* For lip 2, scale it */
	y2 = ScaleChar(params[9], yunit / 3);	/* offset for the second lip (relative to first lip) */
	y2 += temp;
	
	/* Draw the first lip */
	if(temp > 0)						/* if positive, use the bottom (smile) */
	{
		SetRect(&rect, ctr.h - x1, y1 - temp, ctr.h + x1, y1 + temp);
 		FrameArc(&rect, 90, 180);
	}
	else if(temp < 0)					/* if negative, use the top (frown) */
	{
		SetRect(&rect, ctr.h - x1, y1 + temp, ctr.h + x1, y1 - temp);
 		FrameArc(&rect, -90, 180);
	}
	else								/* if neutral, just draw a line */
	{
		MoveTo(ctr.h - x1, y1);
		LineTo(ctr.h + x1 - (xunit / 10), y1);	/* subtract the pen width on the right */
	}
	
	/* Now the second lip */
	temp = y2;
	if(temp > 0)						/* if positive, use the bottom (smile) */
	{
		SetRect(&rect, ctr.h - x1, y1 - temp, ctr.h + x1, y1 + temp);
 		FrameArc(&rect, 90, 180);
	}
	else if(temp < 0)					/* if negative, use the top (frown) */
	{
		SetRect(&rect, ctr.h - x1, y1 + temp, ctr.h + x1, y1 - temp);
 		FrameArc(&rect, -90, 180);
	}
	else								/* if neutral, just draw a line */
	{
		MoveTo(ctr.h - x1, y1);
		LineTo(ctr.h + x1 - (xunit / 10), y1);	/* subtract the pen width on the right */
	}
	
	/* All Done! restore the pen and return */
	SetPenState(&oldpen);
}
