Home | History | Annotate | Download | only in accelerometerplay
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.example.android.accelerometerplay;
     18 
     19 import android.app.Activity;
     20 import android.content.Context;
     21 import android.graphics.Bitmap;
     22 import android.graphics.BitmapFactory;
     23 import android.graphics.Canvas;
     24 import android.graphics.BitmapFactory.Options;
     25 import android.hardware.Sensor;
     26 import android.hardware.SensorEvent;
     27 import android.hardware.SensorEventListener;
     28 import android.hardware.SensorManager;
     29 import android.os.Bundle;
     30 import android.os.PowerManager;
     31 import android.os.PowerManager.WakeLock;
     32 import android.util.DisplayMetrics;
     33 import android.view.Display;
     34 import android.view.Surface;
     35 import android.view.View;
     36 import android.view.WindowManager;
     37 
     38 /**
     39  * This is an example of using the accelerometer to integrate the device's
     40  * acceleration to a position using the Verlet method. This is illustrated with
     41  * a very simple particle system comprised of a few iron balls freely moving on
     42  * an inclined wooden table. The inclination of the virtual table is controlled
     43  * by the device's accelerometer.
     44  *
     45  * @see SensorManager
     46  * @see SensorEvent
     47  * @see Sensor
     48  */
     49 
     50 public class AccelerometerPlayActivity extends Activity {
     51 
     52     private SimulationView mSimulationView;
     53     private SensorManager mSensorManager;
     54     private PowerManager mPowerManager;
     55     private WindowManager mWindowManager;
     56     private Display mDisplay;
     57     private WakeLock mWakeLock;
     58 
     59     /** Called when the activity is first created. */
     60     @Override
     61     public void onCreate(Bundle savedInstanceState) {
     62         super.onCreate(savedInstanceState);
     63 
     64         // Get an instance of the SensorManager
     65         mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
     66 
     67         // Get an instance of the PowerManager
     68         mPowerManager = (PowerManager) getSystemService(POWER_SERVICE);
     69 
     70         // Get an instance of the WindowManager
     71         mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
     72         mDisplay = mWindowManager.getDefaultDisplay();
     73 
     74         // Create a bright wake lock
     75         mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, getClass()
     76                 .getName());
     77 
     78         // instantiate our simulation view and set it as the activity's content
     79         mSimulationView = new SimulationView(this);
     80         setContentView(mSimulationView);
     81     }
     82 
     83     @Override
     84     protected void onResume() {
     85         super.onResume();
     86         /*
     87          * when the activity is resumed, we acquire a wake-lock so that the
     88          * screen stays on, since the user will likely not be fiddling with the
     89          * screen or buttons.
     90          */
     91         mWakeLock.acquire();
     92 
     93         // Start the simulation
     94         mSimulationView.startSimulation();
     95     }
     96 
     97     @Override
     98     protected void onPause() {
     99         super.onPause();
    100         /*
    101          * When the activity is paused, we make sure to stop the simulation,
    102          * release our sensor resources and wake locks
    103          */
    104 
    105         // Stop the simulation
    106         mSimulationView.stopSimulation();
    107 
    108         // and release our wake-lock
    109         mWakeLock.release();
    110     }
    111 
    112     class SimulationView extends View implements SensorEventListener {
    113         // diameter of the balls in meters
    114         private static final float sBallDiameter = 0.004f;
    115         private static final float sBallDiameter2 = sBallDiameter * sBallDiameter;
    116 
    117         // friction of the virtual table and air
    118         private static final float sFriction = 0.1f;
    119 
    120         private Sensor mAccelerometer;
    121         private long mLastT;
    122         private float mLastDeltaT;
    123 
    124         private float mXDpi;
    125         private float mYDpi;
    126         private float mMetersToPixelsX;
    127         private float mMetersToPixelsY;
    128         private Bitmap mBitmap;
    129         private Bitmap mWood;
    130         private float mXOrigin;
    131         private float mYOrigin;
    132         private float mSensorX;
    133         private float mSensorY;
    134         private long mSensorTimeStamp;
    135         private long mCpuTimeStamp;
    136         private float mHorizontalBound;
    137         private float mVerticalBound;
    138         private final ParticleSystem mParticleSystem = new ParticleSystem();
    139 
    140         /*
    141          * Each of our particle holds its previous and current position, its
    142          * acceleration. for added realism each particle has its own friction
    143          * coefficient.
    144          */
    145         class Particle {
    146             private float mPosX;
    147             private float mPosY;
    148             private float mAccelX;
    149             private float mAccelY;
    150             private float mLastPosX;
    151             private float mLastPosY;
    152             private float mOneMinusFriction;
    153 
    154             Particle() {
    155                 // make each particle a bit different by randomizing its
    156                 // coefficient of friction
    157                 final float r = ((float) Math.random() - 0.5f) * 0.2f;
    158                 mOneMinusFriction = 1.0f - sFriction + r;
    159             }
    160 
    161             public void computePhysics(float sx, float sy, float dT, float dTC) {
    162                 // Force of gravity applied to our virtual object
    163                 final float m = 1000.0f; // mass of our virtual object
    164                 final float gx = -sx * m;
    165                 final float gy = -sy * m;
    166 
    167                 /*
    168                  * F = mA <=> A = F / m We could simplify the code by
    169                  * completely eliminating "m" (the mass) from all the equations,
    170                  * but it would hide the concepts from this sample code.
    171                  */
    172                 final float invm = 1.0f / m;
    173                 final float ax = gx * invm;
    174                 final float ay = gy * invm;
    175 
    176                 /*
    177                  * Time-corrected Verlet integration The position Verlet
    178                  * integrator is defined as x(t+t) = x(t) + x(t) - x(t-t) +
    179                  * a(t)t2 However, the above equation doesn't handle variable
    180                  * t very well, a time-corrected version is needed: x(t+t) =
    181                  * x(t) + (x(t) - x(t-t)) * (t/t_prev) + a(t)t2 We also add
    182                  * a simple friction term (f) to the equation: x(t+t) = x(t) +
    183                  * (1-f) * (x(t) - x(t-t)) * (t/t_prev) + a(t)t2
    184                  */
    185                 final float dTdT = dT * dT;
    186                 final float x = mPosX + mOneMinusFriction * dTC * (mPosX - mLastPosX) + mAccelX
    187                         * dTdT;
    188                 final float y = mPosY + mOneMinusFriction * dTC * (mPosY - mLastPosY) + mAccelY
    189                         * dTdT;
    190                 mLastPosX = mPosX;
    191                 mLastPosY = mPosY;
    192                 mPosX = x;
    193                 mPosY = y;
    194                 mAccelX = ax;
    195                 mAccelY = ay;
    196             }
    197 
    198             /*
    199              * Resolving constraints and collisions with the Verlet integrator
    200              * can be very simple, we simply need to move a colliding or
    201              * constrained particle in such way that the constraint is
    202              * satisfied.
    203              */
    204             public void resolveCollisionWithBounds() {
    205                 final float xmax = mHorizontalBound;
    206                 final float ymax = mVerticalBound;
    207                 final float x = mPosX;
    208                 final float y = mPosY;
    209                 if (x > xmax) {
    210                     mPosX = xmax;
    211                 } else if (x < -xmax) {
    212                     mPosX = -xmax;
    213                 }
    214                 if (y > ymax) {
    215                     mPosY = ymax;
    216                 } else if (y < -ymax) {
    217                     mPosY = -ymax;
    218                 }
    219             }
    220         }
    221 
    222         /*
    223          * A particle system is just a collection of particles
    224          */
    225         class ParticleSystem {
    226             static final int NUM_PARTICLES = 15;
    227             private Particle mBalls[] = new Particle[NUM_PARTICLES];
    228 
    229             ParticleSystem() {
    230                 /*
    231                  * Initially our particles have no speed or acceleration
    232                  */
    233                 for (int i = 0; i < mBalls.length; i++) {
    234                     mBalls[i] = new Particle();
    235                 }
    236             }
    237 
    238             /*
    239              * Update the position of each particle in the system using the
    240              * Verlet integrator.
    241              */
    242             private void updatePositions(float sx, float sy, long timestamp) {
    243                 final long t = timestamp;
    244                 if (mLastT != 0) {
    245                     final float dT = (float) (t - mLastT) * (1.0f / 1000000000.0f);
    246                     if (mLastDeltaT != 0) {
    247                         final float dTC = dT / mLastDeltaT;
    248                         final int count = mBalls.length;
    249                         for (int i = 0; i < count; i++) {
    250                             Particle ball = mBalls[i];
    251                             ball.computePhysics(sx, sy, dT, dTC);
    252                         }
    253                     }
    254                     mLastDeltaT = dT;
    255                 }
    256                 mLastT = t;
    257             }
    258 
    259             /*
    260              * Performs one iteration of the simulation. First updating the
    261              * position of all the particles and resolving the constraints and
    262              * collisions.
    263              */
    264             public void update(float sx, float sy, long now) {
    265                 // update the system's positions
    266                 updatePositions(sx, sy, now);
    267 
    268                 // We do no more than a limited number of iterations
    269                 final int NUM_MAX_ITERATIONS = 10;
    270 
    271                 /*
    272                  * Resolve collisions, each particle is tested against every
    273                  * other particle for collision. If a collision is detected the
    274                  * particle is moved away using a virtual spring of infinite
    275                  * stiffness.
    276                  */
    277                 boolean more = true;
    278                 final int count = mBalls.length;
    279                 for (int k = 0; k < NUM_MAX_ITERATIONS && more; k++) {
    280                     more = false;
    281                     for (int i = 0; i < count; i++) {
    282                         Particle curr = mBalls[i];
    283                         for (int j = i + 1; j < count; j++) {
    284                             Particle ball = mBalls[j];
    285                             float dx = ball.mPosX - curr.mPosX;
    286                             float dy = ball.mPosY - curr.mPosY;
    287                             float dd = dx * dx + dy * dy;
    288                             // Check for collisions
    289                             if (dd <= sBallDiameter2) {
    290                                 /*
    291                                  * add a little bit of entropy, after nothing is
    292                                  * perfect in the universe.
    293                                  */
    294                                 dx += ((float) Math.random() - 0.5f) * 0.0001f;
    295                                 dy += ((float) Math.random() - 0.5f) * 0.0001f;
    296                                 dd = dx * dx + dy * dy;
    297                                 // simulate the spring
    298                                 final float d = (float) Math.sqrt(dd);
    299                                 final float c = (0.5f * (sBallDiameter - d)) / d;
    300                                 curr.mPosX -= dx * c;
    301                                 curr.mPosY -= dy * c;
    302                                 ball.mPosX += dx * c;
    303                                 ball.mPosY += dy * c;
    304                                 more = true;
    305                             }
    306                         }
    307                         /*
    308                          * Finally make sure the particle doesn't intersects
    309                          * with the walls.
    310                          */
    311                         curr.resolveCollisionWithBounds();
    312                     }
    313                 }
    314             }
    315 
    316             public int getParticleCount() {
    317                 return mBalls.length;
    318             }
    319 
    320             public float getPosX(int i) {
    321                 return mBalls[i].mPosX;
    322             }
    323 
    324             public float getPosY(int i) {
    325                 return mBalls[i].mPosY;
    326             }
    327         }
    328 
    329         public void startSimulation() {
    330             /*
    331              * It is not necessary to get accelerometer events at a very high
    332              * rate, by using a slower rate (SENSOR_DELAY_UI), we get an
    333              * automatic low-pass filter, which "extracts" the gravity component
    334              * of the acceleration. As an added benefit, we use less power and
    335              * CPU resources.
    336              */
    337             mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_UI);
    338         }
    339 
    340         public void stopSimulation() {
    341             mSensorManager.unregisterListener(this);
    342         }
    343 
    344         public SimulationView(Context context) {
    345             super(context);
    346             mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    347 
    348             DisplayMetrics metrics = new DisplayMetrics();
    349             getWindowManager().getDefaultDisplay().getMetrics(metrics);
    350             mXDpi = metrics.xdpi;
    351             mYDpi = metrics.ydpi;
    352             mMetersToPixelsX = mXDpi / 0.0254f;
    353             mMetersToPixelsY = mYDpi / 0.0254f;
    354 
    355             // rescale the ball so it's about 0.5 cm on screen
    356             Bitmap ball = BitmapFactory.decodeResource(getResources(), R.drawable.ball);
    357             final int dstWidth = (int) (sBallDiameter * mMetersToPixelsX + 0.5f);
    358             final int dstHeight = (int) (sBallDiameter * mMetersToPixelsY + 0.5f);
    359             mBitmap = Bitmap.createScaledBitmap(ball, dstWidth, dstHeight, true);
    360 
    361             Options opts = new Options();
    362             opts.inDither = true;
    363             opts.inPreferredConfig = Bitmap.Config.RGB_565;
    364             mWood = BitmapFactory.decodeResource(getResources(), R.drawable.wood, opts);
    365         }
    366 
    367         @Override
    368         protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    369             // compute the origin of the screen relative to the origin of
    370             // the bitmap
    371             mXOrigin = (w - mBitmap.getWidth()) * 0.5f;
    372             mYOrigin = (h - mBitmap.getHeight()) * 0.5f;
    373             mHorizontalBound = ((w / mMetersToPixelsX - sBallDiameter) * 0.5f);
    374             mVerticalBound = ((h / mMetersToPixelsY - sBallDiameter) * 0.5f);
    375         }
    376 
    377         @Override
    378         public void onSensorChanged(SensorEvent event) {
    379             if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER)
    380                 return;
    381             /*
    382              * record the accelerometer data, the event's timestamp as well as
    383              * the current time. The latter is needed so we can calculate the
    384              * "present" time during rendering. In this application, we need to
    385              * take into account how the screen is rotated with respect to the
    386              * sensors (which always return data in a coordinate space aligned
    387              * to with the screen in its native orientation).
    388              */
    389 
    390             switch (mDisplay.getRotation()) {
    391                 case Surface.ROTATION_0:
    392                     mSensorX = event.values[0];
    393                     mSensorY = event.values[1];
    394                     break;
    395                 case Surface.ROTATION_90:
    396                     mSensorX = -event.values[1];
    397                     mSensorY = event.values[0];
    398                     break;
    399                 case Surface.ROTATION_180:
    400                     mSensorX = -event.values[0];
    401                     mSensorY = -event.values[1];
    402                     break;
    403                 case Surface.ROTATION_270:
    404                     mSensorX = event.values[1];
    405                     mSensorY = -event.values[0];
    406                     break;
    407             }
    408 
    409             mSensorTimeStamp = event.timestamp;
    410             mCpuTimeStamp = System.nanoTime();
    411         }
    412 
    413         @Override
    414         protected void onDraw(Canvas canvas) {
    415 
    416             /*
    417              * draw the background
    418              */
    419 
    420             canvas.drawBitmap(mWood, 0, 0, null);
    421 
    422             /*
    423              * compute the new position of our object, based on accelerometer
    424              * data and present time.
    425              */
    426 
    427             final ParticleSystem particleSystem = mParticleSystem;
    428             final long now = mSensorTimeStamp + (System.nanoTime() - mCpuTimeStamp);
    429             final float sx = mSensorX;
    430             final float sy = mSensorY;
    431 
    432             particleSystem.update(sx, sy, now);
    433 
    434             final float xc = mXOrigin;
    435             final float yc = mYOrigin;
    436             final float xs = mMetersToPixelsX;
    437             final float ys = mMetersToPixelsY;
    438             final Bitmap bitmap = mBitmap;
    439             final int count = particleSystem.getParticleCount();
    440             for (int i = 0; i < count; i++) {
    441                 /*
    442                  * We transform the canvas so that the coordinate system matches
    443                  * the sensors coordinate system with the origin in the center
    444                  * of the screen and the unit is the meter.
    445                  */
    446 
    447                 final float x = xc + particleSystem.getPosX(i) * xs;
    448                 final float y = yc - particleSystem.getPosY(i) * ys;
    449                 canvas.drawBitmap(bitmap, x, y, null);
    450             }
    451 
    452             // and make sure to redraw asap
    453             invalidate();
    454         }
    455 
    456         @Override
    457         public void onAccuracyChanged(Sensor sensor, int accuracy) {
    458         }
    459     }
    460 }
    461