A couple of weeks ago I started to tinker with android programming. After reading some articles in android developers guide I developed my first application. It is a simple application that draws a cube in a 2D canvas. The cube can be controlled with touch input. Below you may see the video of the application in action. I have tested the application on a Samsung Galaxy S2.
The Idea
I have created a custom view by extending the View class. In the custom view I overridden the onDraw() method to draw the cube. I also overridden the onTouchEvent() method to capture touch events.
The Code
CubeView.java:
The CubeView class is the custom view responsible for drawing the cube.
package com.codentronix.android; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Color; import android.graphics.Paint.Style; import android.graphics.Path; public class CubeView extends View { /* Vertices of the cube. */ protected Vector3D vertices[]; /* Define the indices to the vertices of each face of the cube. */ protected int faces[][]; /* Colors of each face of the cube. */ protected int colors[]; /* Orientation of the cube around X axis. */ protected float ax; /* Orientation of the cube around Y axis. */ protected float ay; /* Orientation of the cube around Z axis. */ protected float az; protected float lastTouchX; protected float lastTouchY; /* This constructor is used when the view is created from code. */ public CubeView(Context context) { super(context); } /* This constructor is used when the view is inflated from a XML file. */ public CubeView(Context context, AttributeSet attrs) { super(context, attrs); } /** * Initializes the cube geometry. */ public void initialize() { vertices = new Vector3D[] { new Vector3D(-1, 1, -1), new Vector3D(1, 1, -1), new Vector3D(1, -1, -1), new Vector3D(-1, -1, -1), new Vector3D(-1, 1, 1), new Vector3D(1, 1, 1), new Vector3D(1, -1, 1), new Vector3D(-1, -1, 1) }; // Define the 6 faces of the cube. We specify the indices to the 4 vertices of each face. faces = new int[][] {{0, 1, 2, 3}, {1, 5, 6, 2}, {5, 4, 7, 6}, {4, 0, 3, 7}, {0, 4, 5, 1}, {3, 2, 6, 7}}; // Define the color of each face. colors = new int[] {Color.BLUE, Color.RED, Color.YELLOW, Color.LTGRAY, Color.CYAN, Color.MAGENTA}; } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); // Initialize the cube geometry. initialize(); // Allow the view to receive touch input. setFocusableInTouchMode(true); } @Override public boolean onTouchEvent(MotionEvent event) { if( event.getAction() == MotionEvent.ACTION_DOWN ) { lastTouchX = event.getX(); lastTouchY = event.getY(); }else if( event.getAction() == MotionEvent.ACTION_MOVE ) { float dx = (event.getX() - lastTouchX) / 30.0f; float dy = (event.getY() - lastTouchY) / 30.0f; ax += dy; ay -= dx; postInvalidate(); } return true; } @Override protected void onDraw(Canvas canvas) { Vector3D t[] = new Vector3D[8]; double avgZ[] = new double[6]; int order[] = new int[6]; for( int i = 0; i < 8; i++ ) { // Rotate the vertex around X, next around Y, and then around Z. t[i] = vertices[i].rotateX(ax).rotateY(ay).rotateZ(az); // Finally, map the vertex from 3D to 2D. t[i] = t[i].project(getWidth(), getHeight(), 256, 4); } // Compute the average Z value of each face. for( int i = 0; i < 6; i++ ) { avgZ[i] = (t[faces[i][0]].z + t[faces[i][1]].z + t[faces[i][2]].z + t[faces[i][3]].z) / 4; order[i] = i; } // Next we sort the faces in descending order based on the Z value. // The objective is to draw distant faces first. This is called // the PAINTERS ALGORITHM. So, the visible faces will hide the invisible ones. // The sorting algorithm used is the SELECTION SORT. for( int i = 0; i int iMax = i; for( int j = i + 1; j avgZ[iMax] ) { iMax = j; } } if( iMax != i ) { double dTmp = avgZ[i]; avgZ[i] = avgZ[iMax]; avgZ[iMax] = dTmp; int iTmp = order[i]; order[i] = order[iMax]; order[iMax] = iTmp; } } canvas.drawColor(Color.BLACK); Paint paint = new Paint(); paint.setColor(Color.WHITE); paint.setStyle(Style.FILL); paint.setTextSize(24); canvas.drawText("3D Cube",10,40,paint); paint.setTextSize(12); canvas.drawText("Drag the cube to change its orientation", 10, 80, paint); canvas.drawText("http://codentronix.com", 10, getHeight() - 10, paint); for( int i = 0; i < 6; i++ ) { int index = order[i]; Path p = new Path(); p.moveTo((float)t[faces[index][0]].x, (float)t[faces[index][0]].y); p.lineTo((float)t[faces[index][1]].x, (float)t[faces[index][1]].y); p.lineTo((float)t[faces[index][2]].x, (float)t[faces[index][2]].y); p.lineTo((float)t[faces[index][3]].x, (float)t[faces[index][3]].y); p.close(); paint.setColor(colors[index]); canvas.drawPath(p, paint); } } }
Vector3D.java:
The Vector3D class is be used to define each vertex of the cube in 3D space.
package com.codentronix.android; public class Vector3D { protected double x; protected double y; protected double z; public Vector3D() { x = y = z = 0; } public Vector3D(double x, double y, double z) { this.x = x; this.y = y; this.z = z; } public Vector3D rotateX(double angle) { double rad, cosa, sina, yn, zn; rad = angle * Math.PI / 180; cosa = Math.cos(rad); sina = Math.sin(rad); yn = this.y * cosa - this.z * sina; zn = this.y * sina + this.z * cosa; return new Vector3D(this.x, yn, zn); } public Vector3D rotateY(double angle) { double rad, cosa, sina, xn, zn; rad = angle * Math.PI / 180; cosa = Math.cos(rad); sina = Math.sin(rad); zn = this.z * cosa - this.x * sina; xn = this.z * sina + this.x * cosa; return new Vector3D(xn, this.y, zn); } public Vector3D rotateZ(double angle) { double rad, cosa, sina, xn, yn; rad = angle * Math.PI / 180; cosa = Math.cos(rad); sina = Math.sin(rad); xn = this.x * cosa - this.y * sina; yn = this.x * sina + this.y * cosa; return new Vector3D(xn, yn, this.z); } public Vector3D project(int viewWidth, int viewHeight, float fov, float viewDistance) { double factor, xn, yn; factor = fov / (viewDistance + this.z); xn = this.x * factor + viewWidth / 2; yn = this.y * factor + viewHeight / 2; return new Vector3D(xn, yn, this.z); } }
The methods rotateX(), rotateY(), and rotateZ() rotate the vertex around X, Y, and Z axis respectively. The method project() maps the vertex from 3D space to 2D space.
CubeIn2DCanvasActivity.java:
This file defines the main activity of the application. The activity loads the layout and adds a menu item. The layout is composed by the custom view we defined above.
package com.codentronix.android; import android.app.Activity; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.app.Dialog; import android.os.Bundle; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.Window; public class CubeIn2DCanvasActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Remove the title bar. requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.main); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.main, menu); return true; } @Override protected Dialog onCreateDialog(int id) { AlertDialog.Builder builder = new AlertDialog.Builder(this); if( id == R.id.about ) { builder.setMessage("Developed by Leonel Machava ") .setPositiveButton("OK", null); } return builder.create(); } @Override public boolean onOptionsItemSelected(MenuItem item) { if( item.getItemId() == R.id.about ) { showDialog(R.id.about); } return true; } }
Resource Files
Layout: main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <com.codentronix.android.CubeView android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout>
Menu file: menu.xml
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/about" android:title="Sobre" /> </menu>
If you liked this post, please consider leaving a comment, sharing this post using one of the buttons below, or subscribing to the blog.
Nice Article…
thanks for share..
Thank you
Thank you for the example! I can’t get a working copy because there seems to be a problem with the code in CubeView.java starting at the onDraw method. Is there a way to download the files? I hope you could fix this because I’d love to get a working example of this! Thanks in advance, Mike
There is incomplete code in the CubeView (Selection Sort Algorith). I hope the below code is what it should be, please confirm the same.
for( int i = 0; i < 6;i++)
{
int iMax = i;
for( int j = i + 1; j <6; j++ ) {
if(avgZ[iMax] <avgZ[j] )
{
iMax = j;
}
}
if( iMax != i ) {
double dTmp = avgZ[i];
avgZ[i] = avgZ[iMax];
avgZ[iMax] = dTmp;
int iTmp = order[i];
order[i] = order[iMax];
order[iMax] = iTmp;
}
}
what manifest permissions do ı need to have? and I copied all codes, it works without any problem but my emulator doesnt show anything, What I must do, please help
what manifest permissions do ı need to have? and I copied all codes, it works without any problem but my emulator doesnt show anything, What I must do, please help.
hello @ercan. No special permission is needed to run this code. Have you tried using a real device?
Anybody knows how to set background image excepting colors to this cube?
Hi,
Can we add view into this . Can we set images on it replacing the colors.
Hi,
Can we add view into this . Can we set images on it by replacing the colors.