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.android.gallery3d.app; 18 19 import android.content.Context; 20 import android.hardware.Sensor; 21 import android.hardware.SensorEvent; 22 import android.hardware.SensorEventListener; 23 import android.hardware.SensorManager; 24 import android.os.SystemClock; 25 import android.util.FloatMath; 26 import android.view.Display; 27 import android.view.Surface; 28 import android.view.WindowManager; 29 30 import com.android.gallery3d.common.Utils; 31 import com.android.gallery3d.util.GalleryUtils; 32 33 public class EyePosition { 34 @SuppressWarnings("unused") 35 private static final String TAG = "EyePosition"; 36 37 public interface EyePositionListener { 38 public void onEyePositionChanged(float x, float y, float z); 39 } 40 41 private static final float GYROSCOPE_THRESHOLD = 0.15f; 42 private static final float GYROSCOPE_LIMIT = 10f; 43 private static final int GYROSCOPE_SETTLE_DOWN = 15; 44 private static final float GYROSCOPE_RESTORE_FACTOR = 0.995f; 45 46 private static final float USER_ANGEL = (float) Math.toRadians(10); 47 private static final float USER_ANGEL_COS = FloatMath.cos(USER_ANGEL); 48 private static final float USER_ANGEL_SIN = FloatMath.sin(USER_ANGEL); 49 private static final float MAX_VIEW_RANGE = (float) 0.5; 50 private static final int NOT_STARTED = -1; 51 52 private static final float USER_DISTANCE_METER = 0.3f; 53 54 private Context mContext; 55 private EyePositionListener mListener; 56 private Display mDisplay; 57 // The eyes' position of the user, the origin is at the center of the 58 // device and the unit is in pixels. 59 private float mX; 60 private float mY; 61 private float mZ; 62 63 private final float mUserDistance; // in pixel 64 private final float mLimit; 65 private long mStartTime = NOT_STARTED; 66 private Sensor mSensor; 67 private PositionListener mPositionListener = new PositionListener(); 68 69 private int mGyroscopeCountdown = 0; 70 71 public EyePosition(Context context, EyePositionListener listener) { 72 mContext = context; 73 mListener = listener; 74 mUserDistance = GalleryUtils.meterToPixel(USER_DISTANCE_METER); 75 mLimit = mUserDistance * MAX_VIEW_RANGE; 76 77 WindowManager wManager = (WindowManager) mContext 78 .getSystemService(Context.WINDOW_SERVICE); 79 mDisplay = wManager.getDefaultDisplay(); 80 81 // The 3D effect where the photo albums fan out in 3D based on angle 82 // of device tilt is currently disabled. 83 /* 84 SensorManager sManager = (SensorManager) mContext 85 .getSystemService(Context.SENSOR_SERVICE); 86 mSensor = sManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); 87 if (mSensor == null) { 88 Log.w(TAG, "no gyroscope, use accelerometer instead"); 89 mSensor = sManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); 90 } 91 if (mSensor == null) { 92 Log.w(TAG, "no sensor available"); 93 } 94 */ 95 } 96 97 public void resetPosition() { 98 mStartTime = NOT_STARTED; 99 mX = mY = 0; 100 mZ = -mUserDistance; 101 mListener.onEyePositionChanged(mX, mY, mZ); 102 } 103 104 /* 105 * We assume the user is at the following position 106 * 107 * /|\ user's eye 108 * | / 109 * -G(gravity) | / 110 * |_/ 111 * / |/_____\ -Y (-y direction of device) 112 * user angel 113 */ 114 private void onAccelerometerChanged(float gx, float gy, float gz) { 115 116 float x = gx, y = gy, z = gz; 117 118 switch (mDisplay.getRotation()) { 119 case Surface.ROTATION_90: x = -gy; y= gx; break; 120 case Surface.ROTATION_180: x = -gx; y = -gy; break; 121 case Surface.ROTATION_270: x = gy; y = -gx; break; 122 } 123 124 float temp = x * x + y * y + z * z; 125 float t = -y /temp; 126 127 float tx = t * x; 128 float ty = -1 + t * y; 129 float tz = t * z; 130 131 float length = FloatMath.sqrt(tx * tx + ty * ty + tz * tz); 132 float glength = FloatMath.sqrt(temp); 133 134 mX = Utils.clamp((x * USER_ANGEL_COS / glength 135 + tx * USER_ANGEL_SIN / length) * mUserDistance, 136 -mLimit, mLimit); 137 mY = -Utils.clamp((y * USER_ANGEL_COS / glength 138 + ty * USER_ANGEL_SIN / length) * mUserDistance, 139 -mLimit, mLimit); 140 mZ = -FloatMath.sqrt( 141 mUserDistance * mUserDistance - mX * mX - mY * mY); 142 mListener.onEyePositionChanged(mX, mY, mZ); 143 } 144 145 private void onGyroscopeChanged(float gx, float gy, float gz) { 146 long now = SystemClock.elapsedRealtime(); 147 float distance = (gx > 0 ? gx : -gx) + (gy > 0 ? gy : - gy); 148 if (distance < GYROSCOPE_THRESHOLD 149 || distance > GYROSCOPE_LIMIT || mGyroscopeCountdown > 0) { 150 --mGyroscopeCountdown; 151 mStartTime = now; 152 float limit = mUserDistance / 20f; 153 if (mX > limit || mX < -limit || mY > limit || mY < -limit) { 154 mX *= GYROSCOPE_RESTORE_FACTOR; 155 mY *= GYROSCOPE_RESTORE_FACTOR; 156 mZ = (float) -Math.sqrt( 157 mUserDistance * mUserDistance - mX * mX - mY * mY); 158 mListener.onEyePositionChanged(mX, mY, mZ); 159 } 160 return; 161 } 162 163 float t = (now - mStartTime) / 1000f * mUserDistance * (-mZ); 164 mStartTime = now; 165 166 float x = -gy, y = -gx; 167 switch (mDisplay.getRotation()) { 168 case Surface.ROTATION_90: x = -gx; y= gy; break; 169 case Surface.ROTATION_180: x = gy; y = gx; break; 170 case Surface.ROTATION_270: x = gx; y = -gy; break; 171 } 172 173 mX = Utils.clamp((float) (mX + x * t / Math.hypot(mZ, mX)), 174 -mLimit, mLimit) * GYROSCOPE_RESTORE_FACTOR; 175 mY = Utils.clamp((float) (mY + y * t / Math.hypot(mZ, mY)), 176 -mLimit, mLimit) * GYROSCOPE_RESTORE_FACTOR; 177 178 mZ = -FloatMath.sqrt( 179 mUserDistance * mUserDistance - mX * mX - mY * mY); 180 mListener.onEyePositionChanged(mX, mY, mZ); 181 } 182 183 private class PositionListener implements SensorEventListener { 184 @Override 185 public void onAccuracyChanged(Sensor sensor, int accuracy) { 186 } 187 188 @Override 189 public void onSensorChanged(SensorEvent event) { 190 switch (event.sensor.getType()) { 191 case Sensor.TYPE_GYROSCOPE: { 192 onGyroscopeChanged( 193 event.values[0], event.values[1], event.values[2]); 194 break; 195 } 196 case Sensor.TYPE_ACCELEROMETER: { 197 onAccelerometerChanged( 198 event.values[0], event.values[1], event.values[2]); 199 } 200 } 201 } 202 } 203 204 public void pause() { 205 if (mSensor != null) { 206 SensorManager sManager = (SensorManager) mContext 207 .getSystemService(Context.SENSOR_SERVICE); 208 sManager.unregisterListener(mPositionListener); 209 } 210 } 211 212 public void resume() { 213 if (mSensor != null) { 214 SensorManager sManager = (SensorManager) mContext 215 .getSystemService(Context.SENSOR_SERVICE); 216 sManager.registerListener(mPositionListener, 217 mSensor, SensorManager.SENSOR_DELAY_GAME); 218 } 219 220 mStartTime = NOT_STARTED; 221 mGyroscopeCountdown = GYROSCOPE_SETTLE_DOWN; 222 mX = mY = 0; 223 mZ = -mUserDistance; 224 mListener.onEyePositionChanged(mX, mY, mZ); 225 } 226 } 227