www.xbdev.net xbdev - software development
Monday December 10, 2018
home | about | contact | Donations

     
 

Slice of Java.

Its simple and its powerful...

 

 

 

Pretty 3D Cube

by Ben Kenwright

 

This is a demonstration of the code that we have achieved up to now - using relatively simple techniques of perspective and Euler's rotation equations, we can create a cool 3D cube java demo.  Now if you click the mouse, you'll be able to rotate the cube.  Here is a screenshot of the applet:


 

To run the applet click <here>

 

Below is the code.  The main addition to this code is culling.  Culling is all about not rendering triangle faces that are facing away from us.  Culling is achieved by by using the normal of the triangle, and taking the dot product of our viewing direction with it.  As the dot product returns the cosine of the angle between the two directional vectors.  So if the angle is less than 90 degrees the cosine will be positive - else it will be negative.

I've cut and paste the code which is responsible for determining if we cull the triangle.

 

 

 

Code Snippet - Culling Code

 ....

 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(..)

....

 

Its a lot of code now..hehehe.  But if you look at the applet code, you can basically break it down into separate pieces.  For example the render code and the triangle code and then there's small code functions for the various operations, such as Culling and Perspective.

 

 

 

 

 

 

Applet code: cube.java (Download Source Code)

/*********************************************************************************************/

/*                                                                                           */

/*  Java 3D Engine Basics Tutorials - Tut Cube                                               */

/*  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

 

 

 

 

 

 
 Visitor: 9534626  { 209.237.238.175 } Copyright (c) 2002-2017 xbdev.net - All rights reserved.
Designated tutorial and software are the property of their respective owners.