/*********************************************************************************************/
/*                                                                                           */
/*  Java 3D Engine Basics Tutorials - Tut x.x                                                */
/*  Auth: bkenwright@xbdev.net                                                               */
/*                                                                                           */
/*                                                                                           */
/*********************************************************************************************/

import java.awt.image.*;
import java.awt.*;
import java.applet.*;

/*********************************************************************************************/
/*                                                                                           */
/*  class Triangle                                                                           */
/*  We need a way of representing our triangles...so that we can have a number of them       */
/*  and not have to deal with large numbers of arrays of values and things.  By putting      */
/*  them in a class each triangle is all nice and tidy.                                      */
/*                                                                                           */
/*********************************************************************************************/

class Triangle
{
   float x0, y0, z0;
   float x1, y1, z1;
   float x2, y2, z2;
   Color c;
   
   // Average z
   float avg_z;
   
   // Transformed coords
   float tx0, ty0, tz0;
   float tx1, ty1, tz1;
   float tx2, ty2, tz2;
   Color tc;
   
   // Triangle Normal
   float tnx, tny, tnz;
   
   // Cull our triangle if we can't see it
   boolean bCull;
   
   public Triangle(){} // default constructor
   public Triangle( float x0_, float y0_, float z0_,
                    float x1_, float y1_, float z1_,
                    float x2_, float y2_, float z2_,
                    Color c_  )
   {
       tx0=x0=x0_; ty0=y0=y0_; tz0=z0=z0_;
       tx1=x1=x1_; ty1=y1=y1_; tz1=z1=z1_;
       tx2=x2=x2_; ty2=y2=y2_; tz2=z2=z2_;
       tc = c = c_;
       bCull = false;
   }// End of Triangle(..) constructor
  
   public void Translate( float x, float y, float z )
   {
   			tx0 += x;  ty0 += y;  tz0 += z;
   			tx1 += x;  ty1 += y;  tz1 += z;
   			tx2 += x;  ty2 += y;  tz2 += z;
   }// End of Translate(..)
   
   public void ResetCoords()
   {
     tx0=x0; ty0=y0; tz0=z0;
     tx1=x1; ty1=y1; tz1=z1;
     tx2=x2; ty2=y2; tz2=z2;
     bCull = false;
     
   }// End ResetCoords()
   
   public void RotateX( float angle )
   {
      float cosA = (float)Math.cos(angle);
      float sinA = (float)Math.sin(angle);
      
      float temp_x, temp_y, temp_z;
      
      temp_x =  tx0;
      temp_y =  ty0*cosA - tz0*sinA;
      temp_z =  ty0*sinA + tz0*cosA;
      tx0 = temp_x;  ty0 = temp_y;  tz0 = temp_z;
      
      temp_x =  tx1;
      temp_y =  ty1*cosA - tz1*sinA;
      temp_z =  ty1*sinA + tz1*cosA;
      tx1 = temp_x;  ty1 = temp_y;  tz1 = temp_z;
      
      temp_x =  tx2;
      temp_y =  ty2*cosA - tz2*sinA;
      temp_z =  ty2*sinA + tz2*cosA;
      tx2 = temp_x;  ty2 = temp_y;  tz2 = temp_z;
      
   }// End RotateX(..)
   
  public void RotateY( float angle )
   {
      float cosA = (float)Math.cos(angle);
      float sinA = (float)Math.sin(angle);
      
      float temp_x, temp_y, temp_z;
      
      temp_x =   tx0*cosA + tz0*sinA;
      temp_y =   ty0;
      temp_z =  -tx0*sinA + tz0*cosA;
      tx0 = temp_x;  ty0 = temp_y;  tz0 = temp_z;
      
      temp_x =   tx1*cosA + tz1*sinA;
      temp_y =   ty1;
      temp_z =  -tx1*sinA + tz1*cosA;
      tx1 = temp_x;  ty1 = temp_y;  tz1 = temp_z;
      
      temp_x =   tx2*cosA + tz2*sinA;
      temp_y =   ty2;
      temp_z =  -tx2*sinA + tz2*cosA;
      tx2 = temp_x;  ty2 = temp_y;  tz2 = temp_z;
			 
   }// End RotateY(..)
   
   public void RotateZ( float angle )
   {
      float cosA = (float)Math.cos(angle);
      float sinA = (float)Math.sin(angle);
      
      float temp_x =  tx0*cosA - ty0*sinA;
      float temp_y =  tx0*sinA + ty0*cosA;
      float temp_z =  tz0;
      tx0 = temp_x;  ty0 = temp_y;  tz0 = temp_z;
      
      temp_x =  tx1*cosA - ty1*sinA;
      temp_y =  tx1*sinA + ty1*cosA;
      temp_z =  tz1;
      tx1 = temp_x;  ty1 = temp_y;  tz1 = temp_z;
      
      temp_x =  tx2*cosA - ty2*sinA;
      temp_y =  tx2*sinA + ty2*cosA;
      temp_z =  tz2;
      tx2 = temp_x;  ty2 = temp_y;  tz2 = temp_z;
   }// End RotateZ(..)
   
   public void CalculateNormal()
   {
      // First we need to find the direction of our triangle
      // using the cross product :)
      
      // Lets calculate the two direction vectors
      float vx0, vy0, vz0;
      float vx1, vy1, vz1;
      
      vx0 = tx2 - tx0;
      vy0 = ty2 - ty0;
      vz0 = tz2 - tz0;
    
      vx1 = tx1 - tx0;
      vy1 = ty1 - ty0;
      vz1 = tz1 - tz0;
      
      // Do the cross product
      float nx, ny, nz;
      nx =  vy0*vz1 - vz0*vy1;
      ny = -vx0*vz1 + vz0*vx1;
      nz =  vx0*vy1 - vy0*vx1;
      
      // And we normalize it so its magnitude is 1
      float length = nx*nx + ny*ny + nz*nz;
			length = (float)Math.sqrt(length);
			
			nx /= length;
			ny /= length;
			nz /= length;
			
			tnx = nx;
			tny = ny;
			tnz = nz;
   
   }// End CalculateNormal(..)
   
   
}// End of class triangle



/*********************************************************************************************/
/*                                                                                           */
/*  Program Entry Point                                                                      */
/*                                                                                           */
/*********************************************************************************************/

public class cube extends Applet 
{
   Image myImage;
   Graphics offScreen;
  
   int mouse_x_left=0;
   int mouse_y_left=0;
  
   int mouse_x_right=0;
   int mouse_y_right=0;
  


  
   int m_NumTris = 12;
   Triangle[] m_tri;

   public void init() 
   {
      Dimension appletSize = this.getSize();
      myImage = createImage(appletSize.width,appletSize.height);
      offScreen = myImage.getGraphics();
   	 
      m_tri = new Triangle[m_NumTris];
      // Front
      m_tri[0] = new Triangle(-1.0f, -1.0f, -1.0f,  -1.0f, 1.0f, -1.0f,  1.0f,  1.0f, -1.0f,  Color.red);
      m_tri[1] = new Triangle(-1.0f, -1.0f, -1.0f,   1.0f, 1.0f,-1.0f,   1.0f, -1.0f, -1.0f,  Color.red);
				
      // Back
      m_tri[2] = new Triangle(-1.0f, -1.0f,  1.0f,  1.0f,  1.0f, 1.0f,  -1.0f, 1.0f,  1.0f,  Color.green);
      m_tri[3] = new Triangle(-1.0f, -1.0f,  1.0f,  1.0f, -1.0f, 1.0f,   1.0f, 1.0f,  1.0f,  Color.green);									 
   	 
      // Bottom
      m_tri[4] = new Triangle(-1.0f, -1.0f,  -1.0f,   1.0f, -1.0f, 1.0f,   -1.0f, -1.0f, 1.0f,   Color.blue);
      m_tri[5] = new Triangle(-1.0f, -1.0f,  -1.0f,   1.0f, -1.0f, -1.0f,   1.0f, -1.0f,  1.0f,  Color.blue);	
    
      // Top
      m_tri[6] = new Triangle(-1.0f,  1.0f,  -1.0f,  -1.0f, 1.0f, 1.0f,   1.0f,  1.0f, 1.0f,  Color.orange);
      m_tri[7] = new Triangle(-1.0f,  1.0f,  -1.0f,   1.0f, 1.0f,  1.0f,  1.0f, 1.0f, -1.0f,  Color.orange);
    
      // Left
      m_tri[8] = new Triangle(-1.0f, -1.0f,  -1.0f,   -1.0f,  1.0f, 1.0f,   -1.0f, 1.0f, -1.0f,  Color.yellow);
      m_tri[9] = new Triangle(-1.0f, -1.0f,  -1.0f,   -1.0f, -1.0f, 1.0f,   -1.0f, 1.0f,  1.0f,  Color.yellow);
    
      // Right
      m_tri[10] = new Triangle(1.0f, -1.0f,  -1.0f,   1.0f, 1.0f, -1.0f,   1.0f,  1.0f, 1.0f,  Color.pink);
      m_tri[11] = new Triangle(1.0f, -1.0f,  -1.0f,   1.0f, 1.0f,  1.0f,   1.0f, -1.0f, 1.0f,  Color.pink);
    
    
   }// End of init(..)

  public void RenderTriangle(Triangle t)
  {
      ScreenPerspective( myImage,
                         t.tx0, t.ty0, t.tz0,
                         t.tx1, t.ty1, t.tz1,
                         t.tx2, t.ty2, t.tz2,
                         t.tc);
  }
  
  void SortRenderOrder(Triangle t[], int iNumTris)
  {
     // First lets generate an average z for each triangle
     float average_z;
     for( int i=0; i<iNumTris; i++ )
     {
        average_z = t[i].tz0 + t[i].tz1 + t[i].tz2;
        average_z /= 3.0f;
        t[i].avg_z = average_z;
     }// End for loop
     
     // Now lets do a bruit force sort, so each triangle is in order
     // of our average z
     for( int outer=0; outer<iNumTris; outer++ )
     {
        for( int inner=0; inner<iNumTris; inner++ )
        {
            if( t[outer].avg_z > t[inner].avg_z )
            {
               Triangle temp = t[outer];
               t[outer] = t[inner];
               t[inner] = temp;
            }// End if(..)
        }// End inner for loop
     }// End outer for loop
     
  }// SortRenderOrder(..)
  
  void RenderAllTriangles(Triangle t[], int iNumTris)
  {
     for(int i=0; i<iNumTris; i++)
     {
        if(t[i].bCull == false )
           RenderTriangle( t[i] );
     }// End for loop
  }// End of RenderAllTriangles(..)
  
  float DotProduct(float x0, float y0, float z0,
	                 float x1, float y1, float z1 )
  {
    return( x0*x1 + y0*y1 + z0*z1 );
  }// End of DotProduct(..)
  
  void CullTriangles( Triangle t[], int iNumTris )
  {
     // Our default camera is at 0,0,0 and faces in the positive z
     // direction
     
     for(int i=0; i<iNumTris; i++)
     {
        float cosA = DotProduct( t[i].tnx, t[i].tny, t[i].tnz,
                              0.0f,     0.0f,     1.0f );
                              
        if( cosA <= 0.0f )
           t[i].bCull = true;
        else
           t[i].bCull = false;
     }//End for loop
     
     
  }// End CullTriangles(..)
  
  void CalculateNormals(Triangle t[], int iNumTris)
  {
     for( int i=0; i<iNumTris; i++ )
     {
        t[i].CalculateNormal();
     }// End for loop
  }// End of CalculateNormals(..)
  
  void ResetAllCoords(Triangle t[], int iNumTris)
  {
  	 for(int i=0; i<iNumTris; i++)
     {
        t[i].ResetCoords();  
     }// End for loop
  }// End of ResetAllCoords(..)
  
  float m_AngleX = 0.0f;
  float m_AngleY = 0.0f;
  float m_AngleZ = 0.0f;
  
  public void paint(Graphics g) 
  {
      // Clear screen
      offScreen.setColor(Color.white);
      offScreen.fillRect(0,0,this.getSize().width,this.getSize().height);
			
      Dimension appletSize = this.getSize();
      int width  = appletSize.width;
      int height = appletSize.height;

		      
      ResetAllCoords( m_tri, m_NumTris );
      
      // Lets rotate all of the triangles and translate them back into the horizon
      for(int i=0; i<m_NumTris; i++)
      {
          m_tri[i].RotateY(m_AngleY);
          m_tri[i].RotateZ(m_AngleZ);
          m_tri[i].RotateX(m_AngleX);
          m_tri[i].Translate(0.0f, 0.0f, 3.0f);
      }
      m_AngleY += 0.09f;
      m_AngleZ += 0.12f;
      m_AngleX += 0.06f;

      CalculateNormals( m_tri, m_NumTris );
      CullTriangles( m_tri, m_NumTris );
      SortRenderOrder( m_tri, m_NumTris );
      RenderAllTriangles( m_tri, m_NumTris );


      //offScreen.drawLine( mouse_x_left+10,  mouse_y_left+10,
      //                    mouse_x_right+10, mouse_y_right+10);

      g.drawImage(myImage,0,0,this);

  }// End of paint(..)


  public void update(Graphics g){ paint(g); };
  public void start(){ repaint(); };
  
  public void ScreenPerspective( Image image,
	                               float x0, float y0, float z0,
	                               float x1, float y1, float z1,
	                               float x2, float y2, float z2,
	                               Color c )
  {
       // This is where we'll put our final values that we calculate
       // we only have an x and a y, as we'll convert our x,y and z into
       // a screen coordinate square which has all the sizing and things
       // done to it already

		
       // Get the screen size of our applet
       Dimension appletSize = this.getSize();
       int width  = appletSize.width;
       int height = appletSize.height;
		
		      
       //if( (z0<1) && (z1<1) && (z2<1) )
       //   return;
		         
       //if( z0 < 1 ) z0 = 1;
       //if( z1 < 1 ) z1 = 1;
       //if( z2 < 1 ) z2 = 1;
		      
		         
       // Something worth noting - we have a pole at z=0...as when we
       // create a perspective ..which is x_per = d*x/z for example, if
       // z is 0 or very close to zero, we'll get an infinit number...so
       // if we have a z value less than 1 we'll just round z to 1 and it
       // will get clipped later down the line.
		      
       // Whats this pTan stuff for?...well we can't just clip the z value
       // to 1...as what if its on a really steep angle...so what we do, is
       // we work out a new value for x and z and then clip it to the near
       // clip plane :)
		      
       // Add some clipping code so Z is clipped to the near view plane
       // ++ //
		
       // From x,y,z to x,y for the screen.
       // -1-Perspective Conversion

       // Focul point of 1 (Normalised view plane 90 degrees)
       float d = 1.0f; 
       float Perspective_x0 = -d*x0 / z0;
       float Perspective_x1 = -d*x1 / z1;
       float Perspective_x2 = -d*x2 / z2;
					
       // its between -1 to 1...so we need to scale it to 0 to 1...and scale to
       // the size of the screen.
       Perspective_x0 += 1;
       Perspective_x1 += 1;
       Perspective_x2 += 1; 
       // So now its between 0 and 2 and not -1 to 1;
       Perspective_x0 *= 0.5;
       Perspective_x1 *= 0.5;
       Perspective_x2 *= 0.5;
       // -2- Now we scale it to the size of the screen
       float Screen_x0 = (width-1)*Perspective_x0;
       float Screen_x1 = (width-1)*Perspective_x1;
       float Screen_x2 = (width-1)*Perspective_x2;
					
       // Add some clipping to make sure its on the screen here
       // ++ //
		              
       // Now we do the same for the y value to convert it
       // to screen coordinates … its exactly the same as for the x above,
       // but I've combined all the stages into a single line for each
       // one

       // Note y1 is the top of square, and y2 is the bottom of our square
					
       float Screen_y0 = ((-d*y0 / z0)+1)*0.5f *(height-1);
       float Screen_y1 = ((-d*y1 / z1)+1)*0.5f *(height-1);
       float Screen_y2 = ((-d*y2 / z2)+1)*0.5f *(height-1);

       // Debug information
       /*
          System.out.println( "Color: " + c );
          System.out.println( "x0: " + Screen_x0 + "  y0: " + Screen_y0 );
          System.out.println( "x1: " + Screen_x1 + "  y1: " + Screen_y1 );
          System.out.println( "x2: " + Screen_x2 + "  y2: " + Screen_y2 );
          System.out.println("");
       */

       // Render our data
       drawTriangle( image,
                     Screen_x0, Screen_y0,
                     Screen_x1, Screen_y1,
                     Screen_x2, Screen_y2,c );            

  }// End ScreenPerspective(...)


	public void drawTriangle( Image image,
	                          float x0, float y0,
	                          float x1, float y1,
	                          float x2, float y2,
	                          Color c )
	{
	   //First we sort our points out into y order...where it goes
	   // 0
	   // 2
	   // 1
	   float t; // temp variable
	   if( y1 < y0 )
	   {
	      t  = y0;
	      y0 = y1;
	      y1 = t;
	      
	      t  = x0;
	      x0 = x1;
	      x1 = t;
	   }
		 
		 if( y2 < y0 )
	   {
	      t  = y0;
	      y0 = y2;
	      y2 = t;
	      
	      t  = x0;
	      x0 = x2;
	      x2 = t;
	   }  
		 
		if( y1 < y2 )
	   {
	      t  = y1;
	      y1 = y2;
	      y2 = t;
	      
	      t  = x1;
	      x1 = x2;
	      x2 = t;
	   }     
	   
	   // Next part...
	   // Render top of triangle
	   float x_left  = x0;
	   float x_right = x0;
	   
	   float dy_top    = y2 - y0;
	   float dy_bottom = y1 - y2;
	   
	   float dx_left  = x2 - x0;   // to the middle then we change it
	   float dx_right = x1 - x0;   // all the way to the bottom :)
	   
	   //int dx_left_dir  = dx_left  < 0 ? -1 : 1;
	   //int dx_right_dir = dx_right < 0 ? -1 : 1;
	   
	   //dx_left  *= dx_left_dir;
	   //dx_right *= dx_right_dir;
	   
	   //dx_left  = dx_left  / dy_top;
	   //dx_right = dx_right / dy_top;
	   
		 dx_left  = (x2 - x0) / (y2 - y0);
	   dx_right = (x1 - x0) / (y1 - y0);
	   
	   int s = 0;
	   if( dx_left > dx_right )
	   {
	      t = dx_left;
	      dx_left = dx_right;
	      dx_right = t;
	      s=1;
	   }//End if
	   
	   for(float y=y0; y<y2; y++)
	   {
	        for(float x=x_left; x<x_right; x++)
	        {
	   			   setPixel(image, (int)x, (int)y, c );
		  		}// End inner for loop
		  		x_left  += dx_left;
		  		x_right += dx_right;
	   
	   }// End outer for loop
	
	   // **IMPORTANT UPDATE **
	   // Its important to check that the top of the triangle exists...as its af flat top
	   // then we have to make sure our bottom is okay :)  Not the added lines of code
	   // so its not x0 for the starting point.
     
	   // Now for the bottom of the triangle
		 if( s == 0 )
	   {
	      dx_left = (x1 - x2) / (y1 - y2);
	      x_left = x2;
	   }
	   else
	   {
	      dx_right = (x1 - x2) / (y1 - y2);
	      x_right = x2;
	   }
	
	   
	   for(float y=y2; y<y1; y++)
	   {
	      for(float x=x_left; x<x_right; x++)
	      {
	         setPixel(image, (int)x, (int)y, c);
	      }
	      x_left  += dx_left;
	      x_right += dx_right;
	   }// End outer for loop
		
	
	}//End of drawTriangle(..)

  public void drawLine(Image image, int x0, int y0, int x1, int y1, Color c)
  {
  		float sx = (float)x0;
  		float sy = (float)y0;
  		float ex = (float)x1;
  		float ey = (float)y1;
  		
  		float dx = ex-sx;
  		float dy = ey-sy;	
  		
  		int xdir = dx > 0 ? 1 : -1;
  		int ydir = dy > 0 ? 1 : -1;
  		
  		dx = dx*xdir;
  		dy = dy*ydir;
  		
  		
  		float incx =  dx / dy;
  		float incy =  dy / dx;
  		
  		incx *= xdir;
  		incy *= ydir;
  		
  		if( dy > dx )
  		{
		  		for( int y=0; y<dy; y++)
		  		{
		  				setPixel(image, (int)sx, (int)sy, Color.blue );
		  				sx += incx;
		  				sy += ydir;

		  		}// End for loop(..)
  		}
  		else
  		{
  		    for( int x=0; x<dx; x++)
		  		{
		  				setPixel(image, (int)sx, (int)sy, Color.blue );
		  				sy += incy;
		  				sx += xdir;

		  		}// End for loop(..)
  		}
  		
  }//End of drawline(..)
  
  public void setPixel(Image image, int x, int y, Color color ) 
  {
     Graphics g = image.getGraphics( );
     g.setColor( color );
     g.fillRect( x, y, 1, 1 );
     g.dispose( );
  }// End of setPixel(..)


/*********************************************************************************************/
/*                                                                                           */
/*  Capture event when mouse is pressed                                                      */
/*    Parameters:                                                                            */
/*    evt is The event...                                                                    */
/*    x is the x mouse position                                                              */
/*    y is the y mouse position                                                              */
/*    return whether or not we handled the event                                             */
/*                                                                                           */
/*********************************************************************************************/

	public boolean mouseDown(Event evt, int x, int y)
  {
  	  if(evt.id == Event.MOUSE_DOWN)
      {
          // Right mouse button
      		if( evt.metaDown() == true )
      		{
      				System.out.println( "Right MouseDown" );
      				mouse_x_right = x;
     					mouse_y_right = y;
     					
     					//m_x0 = x;
     					//m_y0 = y;
      		}
      		else // right mouse button
      		{
      				System.out.println( "Left MouseDown" );
      				mouse_x_left = x;
     					mouse_y_left = y;
     					
     					//m_x1 = x;
     					//m_y1 = y;
      		}
      }// End if
    
		 repaint();
		 return true;
	}// End of mouseDown(..)

/*********************************************************************************************/
/*                                                                                           */
/*  Capture mouse release                                                                    */
/*    Parameters:                                                                            */
/*    evt is The event...                                                                    */
/*    x is the x mouse position                                                              */
/*    y is the y mouse position                                                              */
/*    return whether or not we handled the event                                             */
/*                                                                                           */
/*********************************************************************************************/

	public boolean mouseUp(Event evt, int x, int y)
	{

		return false;
	}// End of mouseUp(..)
	
/*********************************************************************************************/
/*                                                                                           */
/*  Capture mouse drags                                                                      */
/*    Parameters:                                                                            */
/*    evt is The event...                                                                    */
/*    x is the x mouse position                                                              */
/*    y is the y mouse position                                                              */
/*    return whether or not we handled the event                                             */
/*                                                                                           */
/*********************************************************************************************/

	public boolean mouseDrag(Event evt, int x, int y)
	{
  	return true;
	}// End of mouseDrag(..)


}// End of our Applet

