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.annotation.TargetApi; 20 import android.app.Activity; 21 import android.content.Context; 22 import android.graphics.Bitmap; 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.Build; 30 import android.os.Bundle; 31 import android.os.PowerManager; 32 import android.os.PowerManager.WakeLock; 33 import android.util.AttributeSet; 34 import android.util.DisplayMetrics; 35 import android.view.Display; 36 import android.view.Surface; 37 import android.view.View; 38 import android.view.ViewGroup; 39 import android.view.WindowManager; 40 import android.widget.FrameLayout; 41 42 /** 43 * This is an example of using the accelerometer to integrate the device's 44 * acceleration to a position using the Verlet method. This is illustrated with 45 * a very simple particle system comprised of a few iron balls freely moving on 46 * an inclined wooden table. The inclination of the virtual table is controlled 47 * by the device's accelerometer. 48 * 49 * @see SensorManager 50 * @see SensorEvent 51 * @see Sensor 52 */ 53 54 public class AccelerometerPlayActivity extends Activity { 55 56 private SimulationView mSimulationView; 57 private SensorManager mSensorManager; 58 private PowerManager mPowerManager; 59 private WindowManager mWindowManager; 60 private Display mDisplay; 61 private WakeLock mWakeLock; 62 63 /** Called when the activity is first created. */ 64 @Override onCreate(Bundle savedInstanceState)65 public void onCreate(Bundle savedInstanceState) { 66 super.onCreate(savedInstanceState); 67 68 // Get an instance of the SensorManager 69 mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); 70 71 // Get an instance of the PowerManager 72 mPowerManager = (PowerManager) getSystemService(POWER_SERVICE); 73 74 // Get an instance of the WindowManager 75 mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE); 76 mDisplay = mWindowManager.getDefaultDisplay(); 77 78 // Create a bright wake lock 79 mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, getClass() 80 .getName()); 81 82 // instantiate our simulation view and set it as the activity's content 83 mSimulationView = new SimulationView(this); 84 mSimulationView.setBackgroundResource(R.drawable.wood); 85 setContentView(mSimulationView); 86 } 87 88 @Override onResume()89 protected void onResume() { 90 super.onResume(); 91 /* 92 * when the activity is resumed, we acquire a wake-lock so that the 93 * screen stays on, since the user will likely not be fiddling with the 94 * screen or buttons. 95 */ 96 mWakeLock.acquire(); 97 98 // Start the simulation 99 mSimulationView.startSimulation(); 100 } 101 102 @Override onPause()103 protected void onPause() { 104 super.onPause(); 105 /* 106 * When the activity is paused, we make sure to stop the simulation, 107 * release our sensor resources and wake locks 108 */ 109 110 // Stop the simulation 111 mSimulationView.stopSimulation(); 112 113 // and release our wake-lock 114 mWakeLock.release(); 115 } 116 117 class SimulationView extends FrameLayout implements SensorEventListener { 118 // diameter of the balls in meters 119 private static final float sBallDiameter = 0.004f; 120 private static final float sBallDiameter2 = sBallDiameter * sBallDiameter; 121 122 private final int mDstWidth; 123 private final int mDstHeight; 124 125 private Sensor mAccelerometer; 126 private long mLastT; 127 128 private float mXDpi; 129 private float mYDpi; 130 private float mMetersToPixelsX; 131 private float mMetersToPixelsY; 132 private float mXOrigin; 133 private float mYOrigin; 134 private float mSensorX; 135 private float mSensorY; 136 private float mHorizontalBound; 137 private float mVerticalBound; 138 private final ParticleSystem mParticleSystem; 139 /* 140 * Each of our particle holds its previous and current position, its 141 * acceleration. for added realism each particle has its own friction 142 * coefficient. 143 */ 144 class Particle extends View { 145 private float mPosX = (float) Math.random(); 146 private float mPosY = (float) Math.random(); 147 private float mVelX; 148 private float mVelY; 149 Particle(Context context)150 public Particle(Context context) { 151 super(context); 152 } 153 Particle(Context context, AttributeSet attrs)154 public Particle(Context context, AttributeSet attrs) { 155 super(context, attrs); 156 } 157 Particle(Context context, AttributeSet attrs, int defStyleAttr)158 public Particle(Context context, AttributeSet attrs, int defStyleAttr) { 159 super(context, attrs, defStyleAttr); 160 } 161 162 @TargetApi(Build.VERSION_CODES.LOLLIPOP) Particle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)163 public Particle(Context context, AttributeSet attrs, int defStyleAttr, 164 int defStyleRes) { 165 super(context, attrs, defStyleAttr, defStyleRes); 166 } 167 computePhysics(float sx, float sy, float dT)168 public void computePhysics(float sx, float sy, float dT) { 169 170 final float ax = -sx/5; 171 final float ay = -sy/5; 172 173 mPosX += mVelX * dT + ax * dT * dT / 2; 174 mPosY += mVelY * dT + ay * dT * dT / 2; 175 176 mVelX += ax * dT; 177 mVelY += ay * dT; 178 } 179 180 /* 181 * Resolving constraints and collisions with the Verlet integrator 182 * can be very simple, we simply need to move a colliding or 183 * constrained particle in such way that the constraint is 184 * satisfied. 185 */ resolveCollisionWithBounds()186 public void resolveCollisionWithBounds() { 187 final float xmax = mHorizontalBound; 188 final float ymax = mVerticalBound; 189 final float x = mPosX; 190 final float y = mPosY; 191 if (x > xmax) { 192 mPosX = xmax; 193 mVelX = 0; 194 } else if (x < -xmax) { 195 mPosX = -xmax; 196 mVelX = 0; 197 } 198 if (y > ymax) { 199 mPosY = ymax; 200 mVelY = 0; 201 } else if (y < -ymax) { 202 mPosY = -ymax; 203 mVelY = 0; 204 } 205 } 206 } 207 208 /* 209 * A particle system is just a collection of particles 210 */ 211 class ParticleSystem { 212 static final int NUM_PARTICLES = 5; 213 private Particle mBalls[] = new Particle[NUM_PARTICLES]; 214 ParticleSystem()215 ParticleSystem() { 216 /* 217 * Initially our particles have no speed or acceleration 218 */ 219 for (int i = 0; i < mBalls.length; i++) { 220 mBalls[i] = new Particle(getContext()); 221 mBalls[i].setBackgroundResource(R.drawable.ball); 222 mBalls[i].setLayerType(LAYER_TYPE_HARDWARE, null); 223 addView(mBalls[i], new ViewGroup.LayoutParams(mDstWidth, mDstHeight)); 224 } 225 } 226 227 /* 228 * Update the position of each particle in the system using the 229 * Verlet integrator. 230 */ updatePositions(float sx, float sy, long timestamp)231 private void updatePositions(float sx, float sy, long timestamp) { 232 final long t = timestamp; 233 if (mLastT != 0) { 234 final float dT = (float) (t - mLastT) / 1000.f /** (1.0f / 1000000000.0f)*/; 235 final int count = mBalls.length; 236 for (int i = 0; i < count; i++) { 237 Particle ball = mBalls[i]; 238 ball.computePhysics(sx, sy, dT); 239 } 240 } 241 mLastT = t; 242 } 243 244 /* 245 * Performs one iteration of the simulation. First updating the 246 * position of all the particles and resolving the constraints and 247 * collisions. 248 */ update(float sx, float sy, long now)249 public void update(float sx, float sy, long now) { 250 // update the system's positions 251 updatePositions(sx, sy, now); 252 253 // We do no more than a limited number of iterations 254 final int NUM_MAX_ITERATIONS = 10; 255 256 /* 257 * Resolve collisions, each particle is tested against every 258 * other particle for collision. If a collision is detected the 259 * particle is moved away using a virtual spring of infinite 260 * stiffness. 261 */ 262 boolean more = true; 263 final int count = mBalls.length; 264 for (int k = 0; k < NUM_MAX_ITERATIONS && more; k++) { 265 more = false; 266 for (int i = 0; i < count; i++) { 267 Particle curr = mBalls[i]; 268 for (int j = i + 1; j < count; j++) { 269 Particle ball = mBalls[j]; 270 float dx = ball.mPosX - curr.mPosX; 271 float dy = ball.mPosY - curr.mPosY; 272 float dd = dx * dx + dy * dy; 273 // Check for collisions 274 if (dd <= sBallDiameter2) { 275 /* 276 * add a little bit of entropy, after nothing is 277 * perfect in the universe. 278 */ 279 dx += ((float) Math.random() - 0.5f) * 0.0001f; 280 dy += ((float) Math.random() - 0.5f) * 0.0001f; 281 dd = dx * dx + dy * dy; 282 // simulate the spring 283 final float d = (float) Math.sqrt(dd); 284 final float c = (0.5f * (sBallDiameter - d)) / d; 285 final float effectX = dx * c; 286 final float effectY = dy * c; 287 curr.mPosX -= effectX; 288 curr.mPosY -= effectY; 289 ball.mPosX += effectX; 290 ball.mPosY += effectY; 291 more = true; 292 } 293 } 294 curr.resolveCollisionWithBounds(); 295 } 296 } 297 } 298 getParticleCount()299 public int getParticleCount() { 300 return mBalls.length; 301 } 302 getPosX(int i)303 public float getPosX(int i) { 304 return mBalls[i].mPosX; 305 } 306 getPosY(int i)307 public float getPosY(int i) { 308 return mBalls[i].mPosY; 309 } 310 } 311 startSimulation()312 public void startSimulation() { 313 /* 314 * It is not necessary to get accelerometer events at a very high 315 * rate, by using a slower rate (SENSOR_DELAY_UI), we get an 316 * automatic low-pass filter, which "extracts" the gravity component 317 * of the acceleration. As an added benefit, we use less power and 318 * CPU resources. 319 */ 320 mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME); 321 } 322 stopSimulation()323 public void stopSimulation() { 324 mSensorManager.unregisterListener(this); 325 } 326 SimulationView(Context context)327 public SimulationView(Context context) { 328 super(context); 329 mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); 330 331 DisplayMetrics metrics = new DisplayMetrics(); 332 getWindowManager().getDefaultDisplay().getMetrics(metrics); 333 mXDpi = metrics.xdpi; 334 mYDpi = metrics.ydpi; 335 mMetersToPixelsX = mXDpi / 0.0254f; 336 mMetersToPixelsY = mYDpi / 0.0254f; 337 338 // rescale the ball so it's about 0.5 cm on screen 339 mDstWidth = (int) (sBallDiameter * mMetersToPixelsX + 0.5f); 340 mDstHeight = (int) (sBallDiameter * mMetersToPixelsY + 0.5f); 341 mParticleSystem = new ParticleSystem(); 342 343 Options opts = new Options(); 344 opts.inDither = true; 345 opts.inPreferredConfig = Bitmap.Config.RGB_565; 346 } 347 348 @Override onSizeChanged(int w, int h, int oldw, int oldh)349 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 350 // compute the origin of the screen relative to the origin of 351 // the bitmap 352 mXOrigin = (w - mDstWidth) * 0.5f; 353 mYOrigin = (h - mDstHeight) * 0.5f; 354 mHorizontalBound = ((w / mMetersToPixelsX - sBallDiameter) * 0.5f); 355 mVerticalBound = ((h / mMetersToPixelsY - sBallDiameter) * 0.5f); 356 } 357 358 @Override onSensorChanged(SensorEvent event)359 public void onSensorChanged(SensorEvent event) { 360 if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) 361 return; 362 /* 363 * record the accelerometer data, the event's timestamp as well as 364 * the current time. The latter is needed so we can calculate the 365 * "present" time during rendering. In this application, we need to 366 * take into account how the screen is rotated with respect to the 367 * sensors (which always return data in a coordinate space aligned 368 * to with the screen in its native orientation). 369 */ 370 371 switch (mDisplay.getRotation()) { 372 case Surface.ROTATION_0: 373 mSensorX = event.values[0]; 374 mSensorY = event.values[1]; 375 break; 376 case Surface.ROTATION_90: 377 mSensorX = -event.values[1]; 378 mSensorY = event.values[0]; 379 break; 380 case Surface.ROTATION_180: 381 mSensorX = -event.values[0]; 382 mSensorY = -event.values[1]; 383 break; 384 case Surface.ROTATION_270: 385 mSensorX = event.values[1]; 386 mSensorY = -event.values[0]; 387 break; 388 } 389 } 390 391 @Override onDraw(Canvas canvas)392 protected void onDraw(Canvas canvas) { 393 /* 394 * Compute the new position of our object, based on accelerometer 395 * data and present time. 396 */ 397 final ParticleSystem particleSystem = mParticleSystem; 398 final long now = System.currentTimeMillis(); 399 final float sx = mSensorX; 400 final float sy = mSensorY; 401 402 particleSystem.update(sx, sy, now); 403 404 final float xc = mXOrigin; 405 final float yc = mYOrigin; 406 final float xs = mMetersToPixelsX; 407 final float ys = mMetersToPixelsY; 408 final int count = particleSystem.getParticleCount(); 409 for (int i = 0; i < count; i++) { 410 /* 411 * We transform the canvas so that the coordinate system matches 412 * the sensors coordinate system with the origin in the center 413 * of the screen and the unit is the meter. 414 */ 415 final float x = xc + particleSystem.getPosX(i) * xs; 416 final float y = yc - particleSystem.getPosY(i) * ys; 417 particleSystem.mBalls[i].setTranslationX(x); 418 particleSystem.mBalls[i].setTranslationY(y); 419 } 420 421 // and make sure to redraw asap 422 invalidate(); 423 } 424 425 @Override onAccuracyChanged(Sensor sensor, int accuracy)426 public void onAccuracyChanged(Sensor sensor, int accuracy) { 427 } 428 } 429 } 430