Sunday, June 3, 2012

Android graphics and animation - pendulum motion and circular motion...

Note : Based on the same animation principles, i have developed this Android game which can be found at Google Play store at
https://play.google.com/store/apps/details?id=com.somitsolutions.training.android.bouncingball

When i was given a task to simulate different functionalities of physics/mechanics, i pondered on it for sometimes. Then i thought to create a simple example app to showcase the concept. this app actually animates the simple pendulum motion on android. it was done using android canvas and sufaceview. you can download the source code from here. the apk is here.

i took the initial angle of the pendulum as PI/4. here is the screen capture of the pendulum motion in the emulator.





the entire locus of the pendulum with respect to time has been divided in 6 parts.

  • the first half of the left to right movement
  • the middle position of the pendulum while moving from left to right
  • the second half of the left to right movement
  • the first half of the right to left movement
  • the middle position of the pendulum while moving from right to left
  • the second half of the right to left movement

the formula of the simple pendulum motion that i have used to calculate the X-Y co-ordinates of the pendulum is

d⍬ = dt/squrt(l/g) where d⍬ is the delta angle shift of the pendulum in the delta time dt.

the crux of this app lies in the class called Panel which is defined as follows:


   
class Panel extends SurfaceView implements SurfaceHolder.Callback{
     
     
     public PendulamThread _thread;
     
     public Panel(Context context) {
      super(context);
       
      
      getHolder().addCallback(this);
      _thread = new PendulamThread(getHolder(), this);
     }

     public void surfaceChanged(SurfaceHolder holder, int format, int width,
       int height) {
      // TODO Auto-generated method stub
      
     }

     public void surfaceCreated(SurfaceHolder holder) {
      // TODO Auto-generated method stub
      
       _thread.setRunning(true);
             _thread.start();
      
     }

     public void surfaceDestroyed(SurfaceHolder holder) {
      // TODO Auto-generated method stub
      
       _thread.setRunning(false);
      
     }
     
     @Override
        public void onDraw(Canvas canvas) {
      
       canvas.drawColor(Color.BLACK);
       anchorX = getWidth()/2;
       
       anchorY = getHeight()/4;
       
       //if(!theCenterBeingDrawn){
       canvas.drawCircle(anchorX - 3, anchorY - 4, 7, mPaint);
        
       
       
       //First Half ... Left To Right
       if(leftToRightMovement == true &&
 rightToLeftMovement ==false && 
atTheMiddlePositionWhileLeftToRight == false &&
atTheMiddlePositionWhileRightToLeft == false &&
firstHalf == true && secondHalf == false){
       
        angleInThePreviousStep = angle;
           
                 angle = angle - dt/Math.sqrt(length/9.81);
                 
                 
           
                 if(angle >0.01){
                  ballX = anchorX - (int)length*(Math.sin(angle));
            
            ballY = anchorY + (int)length*(Math.cos(angle));
                  
            canvas.drawLine(anchorX, anchorY,(float)ballX,(float)ballY,mPaint);
                  
                  canvas.drawCircle((float)ballX , (float)ballY , 14, mPaint );
                 }
                 
                 else{
                  atTheMiddlePositionWhileLeftToRight = true;
                  atTheMiddlePositionWhileRightToLeft = false;
                  leftToRightMovement = true;
                  rightToLeftMovement = false;
                  firstHalf = false;
                  secondHalf = false;
                 }
                  
              
                 
                 return;
                 
       }
       //First Half Left To Right end
       
       //AtTheMiddle while Left To right
       if(atTheMiddlePositionWhileLeftToRight == true &&
 leftToRightMovement == true && 
rightToLeftMovement == false && 
atTheMiddlePositionWhileRightToLeft  == false &&
 firstHalf ==false && secondHalf == false){
       
        angle = 0;
        angleInThePreviousStep = 0;
        flag = true;
        angleAccel = 0;
        angleVelocity = (Math.sqrt(2*9.81*length));
        ballX = anchorX;
        ballY = anchorY + length;
        canvas.drawLine(anchorX, anchorY,(float)ballX,(float)ballY,mPaint);
                 
                 canvas.drawCircle((float)ballX ,(float)ballY , 14, mPaint );
                 
                 atTheMiddlePositionWhileLeftToRight = false;
                 leftToRightMovement = true;
                 rightToLeftMovement = false;
                 atTheMiddlePositionWhileRightToLeft = false;
                 firstHalf = false;
                 secondHalf = true;
                 
                
                 return;
       }
       //at the middle while left to right end
       
       //Left to Right second half
       if(leftToRightMovement == true &&
 rightToLeftMovement == false && 
atTheMiddlePositionWhileLeftToRight == false &&
 atTheMiddlePositionWhileRightToLeft ==false &&
 firstHalf == false && secondHalf == true){
        
        double velocityAtTheBeginning = angleVelocity;//not sure if doing the right thing... forgot mechanics
                
        
        angle += dt/(Math.sqrt(length/9.81));
        
         
        if((initialAngle- angle)>0.01){
        
           ballX = anchorX + (int) (Math.sin(angle) * length); //greater than anchorX
           ballY = anchorY + (int) (Math.cos(angle) * length);//less than anchorY
            
           canvas.drawLine(anchorX, anchorY,(float)ballX,(float)ballY,mPaint);
                     
                 canvas.drawCircle((float)ballX , (float)ballY , 14, mPaint );
                 
        }
        else{
        atTheMiddlePositionWhileLeftToRight = false;
                  leftToRightMovement = false;
                  rightToLeftMovement = true;
                  atTheMiddlePositionWhileRightToLeft = false;
                  firstHalf = true;
                  secondHalf = false;
                  angle = initialAngle;
        }
                 return;
       }
       
       //left to right second half end
       
       ////right to left first half
       if(leftToRightMovement == false &&
 rightToLeftMovement ==true && 
atTheMiddlePositionWhileLeftToRight == false && 
atTheMiddlePositionWhileRightToLeft == false && 
firstHalf == true && secondHalf == false){
        
        angleInThePreviousStep = angle;
           
                 angle = angle - dt/Math.sqrt(length/9.81);
                 
                 
           
                 if(angle >0.01){
                  ballX = anchorX + (int)length*(Math.sin(angle));
            
            ballY = anchorY + (int)length*(Math.cos(angle));
                  
            canvas.drawLine(anchorX, anchorY,(float)ballX,(float)ballY,mPaint);
                  
                  canvas.drawCircle((float)ballX , (float)ballY , 14, mPaint );
                 }
                 
                 else{
                  atTheMiddlePositionWhileLeftToRight = false;
                  atTheMiddlePositionWhileRightToLeft = true;
                  leftToRightMovement = false;
                  rightToLeftMovement = true;
                  firstHalf = false;
                  secondHalf = false;
                 }
                  
                 return;
                 
       }
       
       //Right to left first half end
             
       
       ///at the middle while right to left
       if(atTheMiddlePositionWhileLeftToRight == false && leftToRightMovement == false && 
rightToLeftMovement == true && 
atTheMiddlePositionWhileRightToLeft  == true && 
firstHalf ==false && secondHalf == false){
        
        angle = 0;
        //angleInThePreviousStep = 0;
        //flag = true;
        angleAccel = 0;
        angleVelocity = (Math.sqrt(2*9.81*length));
        ballX = anchorX;
        ballY = anchorY + length;
        canvas.drawLine(anchorX, anchorY,(float)ballX,(float)ballY,mPaint);
                 
                 canvas.drawCircle((float)ballX ,(float)ballY , 14, mPaint );
                 
                 atTheMiddlePositionWhileLeftToRight = false;
                 leftToRightMovement = false;
                 rightToLeftMovement = true;
                 atTheMiddlePositionWhileRightToLeft = false;
                 firstHalf = false;
                 secondHalf = true;
                 
                
                 return;
       }
       //at the middle while right to left end
       
       
       
      
       ///Right to left second half
       if(leftToRightMovement == false &&
 rightToLeftMovement == true && 
atTheMiddlePositionWhileLeftToRight == false && 
atTheMiddlePositionWhileRightToLeft ==false && 
firstHalf == false && secondHalf == true){
        
        double velocityAtTheBeginning = angleVelocity;//not sure if doing the right thing... forgot mechanics
                
        
        angle += dt/(Math.sqrt(length/9.81));
        
         
        if((initialAngle - angle)>0.01){
        
           ballX = anchorX - (int) (Math.sin(angle) * length); //greater than anchorX
           ballY = anchorY + (int) (Math.cos(angle) * length);//less than anchorY
            
           canvas.drawLine(anchorX, anchorY,(float)ballX,(float)ballY,mPaint);
                     
                 canvas.drawCircle((float)ballX , (float)ballY , 14, mPaint );
                 
        }
        else{
         atTheMiddlePositionWhileLeftToRight = false;
                  leftToRightMovement = true;
                  rightToLeftMovement = false;
                  atTheMiddlePositionWhileRightToLeft = false;
                  firstHalf = true;
                  secondHalf = false;
                  angle = initialAngle;
        }
                 return;
       }        
     }
     
    }//onDraw



The thread function has been defined as follows:


class PendulamThread extends Thread {
        private SurfaceHolder _surfaceHolder;
        private Panel _panel;
        private boolean _run = false;

        public PendulamThread(SurfaceHolder surfaceHolder, Panel panel) {
            _surfaceHolder = surfaceHolder;
            _panel = panel;
        }

        public void setRunning(boolean run) {
            _run = run;
        }

        public SurfaceHolder getSurfaceHolder() {
            return _surfaceHolder;
        }

        @Override
        public void run() {
            Canvas c;
            while (_run) {
                c = null;
                try {
                    c = _surfaceHolder.lockCanvas(null);
                    synchronized (_surfaceHolder) {
                        _panel.onDraw(c);
                        Thread.sleep(50);
                       //c.drawColor(Color.BLACK);
                       // _panel.postInvalidateDelayed(10);
                    }
                }
                 catch(InterruptedException e){
                         
                 }
                     
                finally {
                }
                    // do this in a finally so that if an exception is thrown
                    // during the above, we don't leave the Surface in an
                    // inconsistent state
                    if (c != null) {
                        _surfaceHolder.unlockCanvasAndPost(c);
                     }
                 }
             }
        }

And the Activity class is like the below:

package com.somitsolutions.android.pendulamsimulation;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class PendulamSimulationActivity extends Activity {
 
 //private Paint mPaint;
 private boolean leftToRightMovement = true;
 private boolean rightToLeftMovement = false;
 private boolean atTheMiddlePositionWhileLeftToRight = false;
 private boolean atTheMiddlePositionWhileRightToLeft = false;
 private boolean firstHalf = true;
 private boolean secondHalf = false;
 private volatile double ballX = 0;
 private volatile double ballY = 0;
 
 //private boolean theCenterBeingDrawn = false;
 
 double angleAccel = 0.0;
 double angleVelocity = 0;
 double dt = 0.15;
 
 private boolean flag = false;
 
 boolean flagCondition = false;
 
 private int anchorX;
 private int anchorY;
 public static double initialAngle = Math.PI/4;
 public static double angle = Math.PI/4;
 double angleInThePreviousStep = Math.PI/4;
 
 private static final int length = 150;
 
 private Paint mPaint;
 
 private Paint mRefreshPaint;
 
 //SurfaceHolder surfaceHolder;
 
 /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        Panel p = new Panel(this);
       
        mPaint = new Paint();
  mPaint.setColor(Color.YELLOW);
  
  mRefreshPaint = new Paint();
  mRefreshPaint.setColor(Color.BLACK);
  
        setContentView(p);
        
    }
.......
.......
.......
    ///Panel and Pendulum Thread classes go here

i would like to share some issues that i faced while doing this animation. as all of you know that the simple animation is done by removing the trace of a moving object by repainting the previous position with the background color. i was trying to do the same thing by repainting only that part of the screen which was occupied by the pendulum before moving to a new position. however, it was not giving proper result. when i was scratching my head over the issue, suddenly i saw a simple line in android documentation that says if somebody wants to repaint only a portion of the screen, the result may be undefined. so its better to repaint the whole screen for proper animation. and it solved my problem. and hence the first line of the onDraw() is canvas.drawColor(Color.BLACK).

similarly, using the same principles, i have simulated the circular motion. the apk for the same can be download from here. the screen capture of this app is like the following:

i am also trying to do some experimentation with the simulation of different physics functionality using the game engine called Cocos2D. here is the screen capture of one of such experimentation which simulates the circular motion.

hopefully this article will be able to throw some lights on android graphics and animation. 
with this positive hope i would like to end this discussion.

No comments: