| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.example.android.accelerometerplay; |
| |
| import android.app.Activity; |
| import android.content.Context; |
| import android.graphics.Bitmap; |
| import android.graphics.BitmapFactory; |
| import android.graphics.Canvas; |
| import android.graphics.BitmapFactory.Options; |
| import android.hardware.Sensor; |
| import android.hardware.SensorEvent; |
| import android.hardware.SensorEventListener; |
| import android.hardware.SensorManager; |
| import android.os.Bundle; |
| import android.os.PowerManager; |
| import android.os.PowerManager.WakeLock; |
| import android.util.DisplayMetrics; |
| import android.view.Display; |
| import android.view.Surface; |
| import android.view.View; |
| import android.view.WindowManager; |
| |
| /** |
| * This is an example of using the accelerometer to integrate the device's |
| * acceleration to a position using the Verlet method. This is illustrated with |
| * a very simple particle system comprised of a few iron balls freely moving on |
| * an inclined wooden table. The inclination of the virtual table is controlled |
| * by the device's accelerometer. |
| * |
| * @see SensorManager |
| * @see SensorEvent |
| * @see Sensor |
| */ |
| |
| public class AccelerometerPlayActivity extends Activity { |
| |
| private SimulationView mSimulationView; |
| private SensorManager mSensorManager; |
| private PowerManager mPowerManager; |
| private WindowManager mWindowManager; |
| private Display mDisplay; |
| private WakeLock mWakeLock; |
| |
| /** Called when the activity is first created. */ |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| |
| // Get an instance of the SensorManager |
| mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); |
| |
| // Get an instance of the PowerManager |
| mPowerManager = (PowerManager) getSystemService(POWER_SERVICE); |
| |
| // Get an instance of the WindowManager |
| mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE); |
| mDisplay = mWindowManager.getDefaultDisplay(); |
| |
| // Create a bright wake lock |
| mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, getClass() |
| .getName()); |
| |
| // instantiate our simulation view and set it as the activity's content |
| mSimulationView = new SimulationView(this); |
| setContentView(mSimulationView); |
| } |
| |
| @Override |
| protected void onResume() { |
| super.onResume(); |
| /* |
| * when the activity is resumed, we acquire a wake-lock so that the |
| * screen stays on, since the user will likely not be fiddling with the |
| * screen or buttons. |
| */ |
| mWakeLock.acquire(); |
| |
| // Start the simulation |
| mSimulationView.startSimulation(); |
| } |
| |
| @Override |
| protected void onPause() { |
| super.onPause(); |
| /* |
| * When the activity is paused, we make sure to stop the simulation, |
| * release our sensor resources and wake locks |
| */ |
| |
| // Stop the simulation |
| mSimulationView.stopSimulation(); |
| |
| // and release our wake-lock |
| mWakeLock.release(); |
| } |
| |
| class SimulationView extends View implements SensorEventListener { |
| // diameter of the balls in meters |
| private static final float sBallDiameter = 0.004f; |
| private static final float sBallDiameter2 = sBallDiameter * sBallDiameter; |
| |
| // friction of the virtual table and air |
| private static final float sFriction = 0.1f; |
| |
| private Sensor mAccelerometer; |
| private long mLastT; |
| private float mLastDeltaT; |
| |
| private float mXDpi; |
| private float mYDpi; |
| private float mMetersToPixelsX; |
| private float mMetersToPixelsY; |
| private Bitmap mBitmap; |
| private Bitmap mWood; |
| private float mXOrigin; |
| private float mYOrigin; |
| private float mSensorX; |
| private float mSensorY; |
| private long mSensorTimeStamp; |
| private long mCpuTimeStamp; |
| private float mHorizontalBound; |
| private float mVerticalBound; |
| private final ParticleSystem mParticleSystem = new ParticleSystem(); |
| |
| /* |
| * Each of our particle holds its previous and current position, its |
| * acceleration. for added realism each particle has its own friction |
| * coefficient. |
| */ |
| class Particle { |
| private float mPosX; |
| private float mPosY; |
| private float mAccelX; |
| private float mAccelY; |
| private float mLastPosX; |
| private float mLastPosY; |
| private float mOneMinusFriction; |
| |
| Particle() { |
| // make each particle a bit different by randomizing its |
| // coefficient of friction |
| final float r = ((float) Math.random() - 0.5f) * 0.2f; |
| mOneMinusFriction = 1.0f - sFriction + r; |
| } |
| |
| public void computePhysics(float sx, float sy, float dT, float dTC) { |
| // Force of gravity applied to our virtual object |
| final float m = 1000.0f; // mass of our virtual object |
| final float gx = -sx * m; |
| final float gy = -sy * m; |
| |
| /* |
| * ·F = mA <=> A = ·F / m We could simplify the code by |
| * completely eliminating "m" (the mass) from all the equations, |
| * but it would hide the concepts from this sample code. |
| */ |
| final float invm = 1.0f / m; |
| final float ax = gx * invm; |
| final float ay = gy * invm; |
| |
| /* |
| * Time-corrected Verlet integration The position Verlet |
| * integrator is defined as x(t+Æt) = x(t) + x(t) - x(t-Æt) + |
| * a(t)Ætö2 However, the above equation doesn't handle variable |
| * Æt very well, a time-corrected version is needed: x(t+Æt) = |
| * x(t) + (x(t) - x(t-Æt)) * (Æt/Æt_prev) + a(t)Ætö2 We also add |
| * a simple friction term (f) to the equation: x(t+Æt) = x(t) + |
| * (1-f) * (x(t) - x(t-Æt)) * (Æt/Æt_prev) + a(t)Ætö2 |
| */ |
| final float dTdT = dT * dT; |
| final float x = mPosX + mOneMinusFriction * dTC * (mPosX - mLastPosX) + mAccelX |
| * dTdT; |
| final float y = mPosY + mOneMinusFriction * dTC * (mPosY - mLastPosY) + mAccelY |
| * dTdT; |
| mLastPosX = mPosX; |
| mLastPosY = mPosY; |
| mPosX = x; |
| mPosY = y; |
| mAccelX = ax; |
| mAccelY = ay; |
| } |
| |
| /* |
| * Resolving constraints and collisions with the Verlet integrator |
| * can be very simple, we simply need to move a colliding or |
| * constrained particle in such way that the constraint is |
| * satisfied. |
| */ |
| public void resolveCollisionWithBounds() { |
| final float xmax = mHorizontalBound; |
| final float ymax = mVerticalBound; |
| final float x = mPosX; |
| final float y = mPosY; |
| if (x > xmax) { |
| mPosX = xmax; |
| } else if (x < -xmax) { |
| mPosX = -xmax; |
| } |
| if (y > ymax) { |
| mPosY = ymax; |
| } else if (y < -ymax) { |
| mPosY = -ymax; |
| } |
| } |
| } |
| |
| /* |
| * A particle system is just a collection of particles |
| */ |
| class ParticleSystem { |
| static final int NUM_PARTICLES = 15; |
| private Particle mBalls[] = new Particle[NUM_PARTICLES]; |
| |
| ParticleSystem() { |
| /* |
| * Initially our particles have no speed or acceleration |
| */ |
| for (int i = 0; i < mBalls.length; i++) { |
| mBalls[i] = new Particle(); |
| } |
| } |
| |
| /* |
| * Update the position of each particle in the system using the |
| * Verlet integrator. |
| */ |
| private void updatePositions(float sx, float sy, long timestamp) { |
| final long t = timestamp; |
| if (mLastT != 0) { |
| final float dT = (float) (t - mLastT) * (1.0f / 1000000000.0f); |
| if (mLastDeltaT != 0) { |
| final float dTC = dT / mLastDeltaT; |
| final int count = mBalls.length; |
| for (int i = 0; i < count; i++) { |
| Particle ball = mBalls[i]; |
| ball.computePhysics(sx, sy, dT, dTC); |
| } |
| } |
| mLastDeltaT = dT; |
| } |
| mLastT = t; |
| } |
| |
| /* |
| * Performs one iteration of the simulation. First updating the |
| * position of all the particles and resolving the constraints and |
| * collisions. |
| */ |
| public void update(float sx, float sy, long now) { |
| // update the system's positions |
| updatePositions(sx, sy, now); |
| |
| // We do no more than a limited number of iterations |
| final int NUM_MAX_ITERATIONS = 10; |
| |
| /* |
| * Resolve collisions, each particle is tested against every |
| * other particle for collision. If a collision is detected the |
| * particle is moved away using a virtual spring of infinite |
| * stiffness. |
| */ |
| boolean more = true; |
| final int count = mBalls.length; |
| for (int k = 0; k < NUM_MAX_ITERATIONS && more; k++) { |
| more = false; |
| for (int i = 0; i < count; i++) { |
| Particle curr = mBalls[i]; |
| for (int j = i + 1; j < count; j++) { |
| Particle ball = mBalls[j]; |
| float dx = ball.mPosX - curr.mPosX; |
| float dy = ball.mPosY - curr.mPosY; |
| float dd = dx * dx + dy * dy; |
| // Check for collisions |
| if (dd <= sBallDiameter2) { |
| /* |
| * add a little bit of entropy, after nothing is |
| * perfect in the universe. |
| */ |
| dx += ((float) Math.random() - 0.5f) * 0.0001f; |
| dy += ((float) Math.random() - 0.5f) * 0.0001f; |
| dd = dx * dx + dy * dy; |
| // simulate the spring |
| final float d = (float) Math.sqrt(dd); |
| final float c = (0.5f * (sBallDiameter - d)) / d; |
| curr.mPosX -= dx * c; |
| curr.mPosY -= dy * c; |
| ball.mPosX += dx * c; |
| ball.mPosY += dy * c; |
| more = true; |
| } |
| } |
| /* |
| * Finally make sure the particle doesn't intersects |
| * with the walls. |
| */ |
| curr.resolveCollisionWithBounds(); |
| } |
| } |
| } |
| |
| public int getParticleCount() { |
| return mBalls.length; |
| } |
| |
| public float getPosX(int i) { |
| return mBalls[i].mPosX; |
| } |
| |
| public float getPosY(int i) { |
| return mBalls[i].mPosY; |
| } |
| } |
| |
| public void startSimulation() { |
| /* |
| * It is not necessary to get accelerometer events at a very high |
| * rate, by using a slower rate (SENSOR_DELAY_UI), we get an |
| * automatic low-pass filter, which "extracts" the gravity component |
| * of the acceleration. As an added benefit, we use less power and |
| * CPU resources. |
| */ |
| mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_UI); |
| } |
| |
| public void stopSimulation() { |
| mSensorManager.unregisterListener(this); |
| } |
| |
| public SimulationView(Context context) { |
| super(context); |
| mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); |
| |
| DisplayMetrics metrics = new DisplayMetrics(); |
| getWindowManager().getDefaultDisplay().getMetrics(metrics); |
| mXDpi = metrics.xdpi; |
| mYDpi = metrics.ydpi; |
| mMetersToPixelsX = mXDpi / 0.0254f; |
| mMetersToPixelsY = mYDpi / 0.0254f; |
| |
| // rescale the ball so it's about 0.5 cm on screen |
| Bitmap ball = BitmapFactory.decodeResource(getResources(), R.drawable.ball); |
| final int dstWidth = (int) (sBallDiameter * mMetersToPixelsX + 0.5f); |
| final int dstHeight = (int) (sBallDiameter * mMetersToPixelsY + 0.5f); |
| mBitmap = Bitmap.createScaledBitmap(ball, dstWidth, dstHeight, true); |
| |
| Options opts = new Options(); |
| opts.inDither = true; |
| opts.inPreferredConfig = Bitmap.Config.RGB_565; |
| mWood = BitmapFactory.decodeResource(getResources(), R.drawable.wood, opts); |
| } |
| |
| @Override |
| protected void onSizeChanged(int w, int h, int oldw, int oldh) { |
| // compute the origin of the screen relative to the origin of |
| // the bitmap |
| mXOrigin = (w - mBitmap.getWidth()) * 0.5f; |
| mYOrigin = (h - mBitmap.getHeight()) * 0.5f; |
| mHorizontalBound = ((w / mMetersToPixelsX - sBallDiameter) * 0.5f); |
| mVerticalBound = ((h / mMetersToPixelsY - sBallDiameter) * 0.5f); |
| } |
| |
| @Override |
| public void onSensorChanged(SensorEvent event) { |
| if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) |
| return; |
| /* |
| * record the accelerometer data, the event's timestamp as well as |
| * the current time. The latter is needed so we can calculate the |
| * "present" time during rendering. In this application, we need to |
| * take into account how the screen is rotated with respect to the |
| * sensors (which always return data in a coordinate space aligned |
| * to with the screen in its native orientation). |
| */ |
| |
| switch (mDisplay.getRotation()) { |
| case Surface.ROTATION_0: |
| mSensorX = event.values[0]; |
| mSensorY = event.values[1]; |
| break; |
| case Surface.ROTATION_90: |
| mSensorX = -event.values[1]; |
| mSensorY = event.values[0]; |
| break; |
| case Surface.ROTATION_180: |
| mSensorX = -event.values[0]; |
| mSensorY = -event.values[1]; |
| break; |
| case Surface.ROTATION_270: |
| mSensorX = event.values[1]; |
| mSensorY = -event.values[0]; |
| break; |
| } |
| |
| mSensorTimeStamp = event.timestamp; |
| mCpuTimeStamp = System.nanoTime(); |
| } |
| |
| @Override |
| protected void onDraw(Canvas canvas) { |
| |
| /* |
| * draw the background |
| */ |
| |
| canvas.drawBitmap(mWood, 0, 0, null); |
| |
| /* |
| * compute the new position of our object, based on accelerometer |
| * data and present time. |
| */ |
| |
| final ParticleSystem particleSystem = mParticleSystem; |
| final long now = mSensorTimeStamp + (System.nanoTime() - mCpuTimeStamp); |
| final float sx = mSensorX; |
| final float sy = mSensorY; |
| |
| particleSystem.update(sx, sy, now); |
| |
| final float xc = mXOrigin; |
| final float yc = mYOrigin; |
| final float xs = mMetersToPixelsX; |
| final float ys = mMetersToPixelsY; |
| final Bitmap bitmap = mBitmap; |
| final int count = particleSystem.getParticleCount(); |
| for (int i = 0; i < count; i++) { |
| /* |
| * We transform the canvas so that the coordinate system matches |
| * the sensors coordinate system with the origin in the center |
| * of the screen and the unit is the meter. |
| */ |
| |
| final float x = xc + particleSystem.getPosX(i) * xs; |
| final float y = yc - particleSystem.getPosY(i) * ys; |
| canvas.drawBitmap(bitmap, x, y, null); |
| } |
| |
| // and make sure to redraw asap |
| invalidate(); |
| } |
| |
| @Override |
| public void onAccuracyChanged(Sensor sensor, int accuracy) { |
| } |
| } |
| } |