/**
*  Face.java - implements the Chernoff faces generation routines
*
*          Based on Faces.c, by Dave Johnson, who got it from Clifford
*          Pickover's Chernoff face routine, in the book "Computers,
*          Pattern, Chaos, and Beauty." Inspired by Brad Mohr's Faces
*          plug-in for AfterDark screen saver for Macintosh.
* 
*          The params are as follows, and are stored in a ten byte array
*          with values ranging from -127 to 127:
*
*          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"
*/

import java.lang.*;
import java.awt.*;

public class Face extends Panel {

  public void CenterRectOverPoint( Rectangle theRect, Point thePt ) {
    int t1, t2 = 0;
    // first home theRect
    theRect.move(0,0);

    // center it over thePt
    t1 = theRect.x + (thePt.x - (theRect.width / 2));
    t2 = theRect.y + (thePt.y - (theRect.height / 2));
    theRect.move(t1,t2);
  }

  public Point GetCenter(Rectangle theRect) {
    Point pt = new Point(0,0);
    pt.x = theRect.x + (theRect.width / 2);
    pt.y = theRect.y + (theRect.height / 2);
    return(pt);
  }

  public int ScaleChar( int param, int max ) {
    return (param * max) / 127;
  }

  public void InsetRect( Rectangle theRect, int dh, int dv) {
    Point curCtr = GetCenter(theRect);
    theRect.x += dh;
    theRect.width -= dh;
    theRect.y += dv;
    theRect.height -= dv;
    if((theRect.width < 1) || (theRect.height < 1)) {
      SetRect(theRect, 0, 0, 0, 0);
    }
    CenterRectOverPoint(theRect, curCtr);
  }

  public void SetRect( Rectangle theRect, int left, int top, int right, int bottom ) {
    if( right < 0 ) right = -right;
    if( bottom < 0 ) bottom = -bottom;
    theRect.reshape( left, top, right, bottom );
  }

  public void OffsetRect( Rectangle theRect, int dh, int dv ) {
    theRect.x += dh;
    theRect.y += dv;
  }

  public void FrameOval( Rectangle theRect, Graphics g ) {
    g.drawOval( theRect.x, theRect.y, theRect.width, theRect.height );
  }

  public void PaintOval( Rectangle theRect, Graphics g ) {
    g.fillOval( theRect.x, theRect.y, theRect.width, theRect.height );
  }

  // Set up 
  public void DrawFace( Rectangle bounds, int[] params, Graphics g ) {
    Rectangle   rect    = new Rectangle();
    Rectangle   tRect   = new Rectangle();
    Rectangle   eyeRect = new Rectangle();
    Rectangle   mRect   = new Rectangle();

    int         i, x1, x2, y1, y2, t1, t2 = 0;
    int         xunit, yunit, temp = 0;
    Point       ctr    = new Point(0,0);
    Point       eyectr = new Point(0,0);
    Point       teyectr = new Point(0,0);

    // These are our basic units in x and y directions
    xunit = ((bounds.width) / 8);
    yunit = ((bounds.height) / 8);

    // the center of the face
    ctr = GetCenter(bounds);

    //
    // draw the head
    //
    // inset by one unit so there is room to squish around
    InsetRect(bounds, xunit, yunit);

    // now add eccentricity (param[0], positive is taller. Get from HTML?)
    t1 = ScaleChar(  params[0], (xunit / 2) );
    t2 = ScaleChar( -params[0], (yunit / 2) );
    InsetRect(bounds, t1, t2);

    // clear the oval to the background color
    Color tempColor = g.getColor();
    g.setColor(Color.black);
    PaintOval(bounds, g);
    g.setColor(tempColor);

    // draw the head oval
    FrameOval(bounds, g);

    //
    // draw the eyes
    //
    // set up the default eye rect
    t1 = ((5 * xunit) / 4);
    t2 = ((5 * yunit) / 4);
    SetRect(eyeRect, 0, 0, t1, t2);
    
    // change the size (params[7], positive is bigger.)
    t1 = ScaleChar(-params[7], xunit / 4);
    t2 = ScaleChar(-params[7], yunit / 4);
    InsetRect(eyeRect, t1, t2);

    // Add eccentricity (params[1], positive is taller)
    t1 = ScaleChar(params[1], xunit / 4);
    t2 = ScaleChar(-params[1], yunit / 4);
    InsetRect(eyeRect, t1, t2);

    // 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(eyeRect, ctr);
    
    // Save the current settings for the other eye - must new up another object
    tRect = new Rectangle(eyeRect.x, eyeRect.y, eyeRect.width, eyeRect.height);
    t1 = -(xunit + temp);
    t2 = -((2 * yunit) / 3);
    OffsetRect(eyeRect, t1, t2);
    FrameOval(eyeRect, g);

    // Draw the pupil, params[2] is diameter, bigger is bigger. We want at
    // least one 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, minimum of 2
    y1 = ScaleChar(x1, yunit / 2);
    if(y1 < 2) { y1 = 2; }
    x1 = ScaleChar(x1, xunit / 2);
    if(x1 < 2) { x1 = 2; }
    
    // Get the center of the eye rect
    eyectr = GetCenter(eyeRect);
    
    // Set up the rect and draw it
    SetRect(eyeRect, 0, 0, 0, 0);
    InsetRect(eyeRect, -x1, -y1);
    CenterRectOverPoint(eyeRect, eyectr);
    PaintOval(eyeRect, g);
    
    // Other eye...
    t1 = xunit + temp;
    t2 = (- ((2 * yunit) / 3));
    OffsetRect(tRect, t1, t2);
    FrameOval(tRect, g);
    
    // Pupil... 
    teyectr = GetCenter(tRect);

    // set up the rect and draw it
    SetRect(tRect, 0, 0, 0, 0);
    InsetRect(tRect, -x1, -y1);
    CenterRectOverPoint(tRect, teyectr);
    PaintOval(tRect, g);

    //
    // Eyebrows
    //
    
    // params[3] is slope, positive slopes down to the outside
    temp = ScaleChar(params[3], yunit / 2);
    y1 = y2 = ctr.y - (yunit + (2 * yunit) / 3);
    y1 += temp;
    y2 -= temp;

    g.drawLine((ctr.x - (3 * xunit) / 2), y1,
	     (ctr.x - xunit / 2), y2);
    g.drawLine((ctr.x + (3 * xunit) / 2), y1,
	     (ctr.x + xunit / 2), y2);

    // Nose
    
    // params[4] is length, positive is bigger
    y1 = yunit + (yunit / 3) + ScaleChar(params[4], yunit / 3);
    
    g.drawLine(ctr.x, (ctr.y - (yunit / 3)),
	       (ctr.x + (xunit / 2)), (ctr.y + y1));
    g.drawLine(ctr.x + (xunit / 2), (ctr.y + y1),
	       ctr.x - (xunit / 2), (ctr.y + y1));

    //
    // Mouth
    //
    // params[8] is width, positive is wider
    temp = ScaleChar(params[8], (xunit / 2));
    y1 = ctr.y + (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
    y2 += temp;

    // Draw the first lip
    if(temp > 0) {			// if positive, use the bottom (smile)
      mRect.x = (ctr.x - x1);
      mRect.y = (y1 - temp);
      mRect.width = (mRect.x - (ctr.x + x1));
      mRect.height = (mRect.y - (y1 + temp));
      g.drawArc( mRect.x,
		 mRect.y,
		 Math.abs(mRect.width),
		 Math.abs(mRect.height),
		 0,
		 180 );
    }
    else if(temp < 0) {			// if negative, use the top (frown)
      mRect.x = (ctr.x - x1);
      mRect.y = (y1 + temp);
      mRect.width = (mRect.x - (ctr.x + x1));
      mRect.height = (mRect.y - (y1 - temp));
      g.drawArc( mRect.x,
		 mRect.y,
		 Math.abs(mRect.width),
		 Math.abs(mRect.height),
		 -180,
		 180 );

    }
    else {			        // if neutral, just draw a line
      // subtract the pen width on the right
      g.drawLine((ctr.x - x1), y1, (ctr.x + x1 - (xunit / 10)), y1);
    }
    
    // Now the second lip
    temp = y2;
    if(temp > 0) {		      // if positive, use the bottom (smile)
      mRect.x = (ctr.x - x1);
      mRect.y = (y1 - temp);
      mRect.width = (mRect.x - (ctr.x + x1));
      mRect.height = (mRect.y - (y1 + temp));
      g.drawArc( mRect.x,
		 mRect.y,
		 Math.abs(mRect.width),
		 Math.abs(mRect.height),
		 0,
		 180 );
    }
    else if(temp < 0) {		      // if negative, use the top (frown)
      mRect.x = (ctr.x - x1);
      mRect.y = (y1 + temp);
      mRect.width = (mRect.x - (ctr.x + x1));
      mRect.height = (mRect.y - (y1 - temp));
      g.drawArc( mRect.x,
		 mRect.y,
		 Math.abs(mRect.width),
		 Math.abs(mRect.height),
		 -180,
		 180 );
    }
    else {			      // if neutral, just draw a line 
      g.drawLine((ctr.x - x1), y1, (ctr.x + x1 - (xunit / 10)), y1);
    }
  }
}








