1 package com.cooliris.media;
3 import android.os.Bundle;
5 public final class GridCamera {
6 public static final float MAX_CAMERA_SPEED = 12.0f;
7 public static final float EYE_CONVERGENCE_SPEED = 3.0f;
8 public static final float EYE_X = 0;
9 public static final float EYE_Y = 0;
10 public static final float EYE_Z = 8.0f; // Initial z distance.
11 private static final float DEFAULT_PORTRAIT_ASPECT = 320.0f / 480.0f;
12 private static final float DEFAULT_LANDSCAPE_ASPECT = 1.0f / DEFAULT_PORTRAIT_ASPECT;
17 public float mLookAtX;
18 public float mLookAtY;
19 public float mLookAtZ;
25 public float mEyeOffsetX;
26 public float mEyeOffsetY;
27 private float mEyeEdgeOffsetX;
28 private float mEyeEdgeOffsetXAnim;
29 private float mAmountExceeding;
32 // Animation speed, 1.0f is normal speed.
33 public float mConvergenceSpeed;
35 // Camera field of view and its relation to the grid item width.
38 public float mOneByScale;
41 public int mItemHeight;
42 public int mItemWidth;
43 public float mAspectRatio;
44 public float mDefaultAspectRatio;
46 // Camera positional information.
50 private float mTargetPosX;
51 private float mTargetPosY;
52 private float mTargetPosZ;
53 private float mEyeOffsetAnimX;
54 private float mEyeOffsetAnimY;
55 private float mTargetEyeX;
57 // Screen width and height.
58 private int mWidthBy2;
59 private int mHeightBy2;
60 private float mTanFovBy2;
62 public GridCamera(int width, int height, int itemWidth, int itemHeight) {
64 viewportChanged(width, height, itemWidth, itemHeight);
65 mConvergenceSpeed = 1.0f;
68 public void onRestoreInstanceState(Bundle savedInstanceState) {
69 mEyeX = savedInstanceState.getInt(new String("Camera.mEyeX")) + EYE_X;
70 mTargetPosX = savedInstanceState.getFloat(new String("Camera.mTargetPosX"));
71 mTargetPosY = savedInstanceState.getFloat(new String("Camera.mTargetPosY"));
72 mTargetPosZ = savedInstanceState.getFloat(new String("Camera.mTargetPosZ"));
76 public void onSaveInstanceState(Bundle outState) {
77 outState.putFloat(new String("Camera.mEyeX"), mEyeX - EYE_X);
78 outState.putFloat(new String("Camera.mTargetPosX"), mTargetPosX);
79 outState.putFloat(new String("Camera.mTargetPosY"), mTargetPosY);
80 outState.putFloat(new String("Camera.mTargetPosZ"), mTargetPosZ);
102 public void viewportChanged(int w, int h, float itemWidth, float itemHeight) {
103 // For pixel precision we need to use this formula.
104 /* fov = 2tan-1(qFactor/2*defaultZ) where qFactor = height/ItemHeight */
105 float qFactor = h / (float) itemHeight;
106 float fov = 2.0f * (float) Math.toDegrees(Math.atan2(qFactor / 2, GridCamera.EYE_Z));
111 mAspectRatio = (h == 0) ? 1.0f : (float)w / (float)h;
112 mDefaultAspectRatio = (w > h) ? DEFAULT_LANDSCAPE_ASPECT : DEFAULT_PORTRAIT_ASPECT;
113 mTanFovBy2 = (float) Math.tan(Math.toRadians(fov * 0.5f));
114 mItemHeight = (int) itemHeight;
115 mItemWidth = (int) itemWidth;
117 mOneByScale = 1.0f / (float) itemHeight;
121 public void convertToScreenSpace(int posX, int posY, int posZ, Vector3f retVal) {
125 public void convertToCameraSpace(float posX, float posY, float posZ, Vector3f retVal) {
126 float posXx = posX - mWidthBy2;
127 float posYx = posY - mHeightBy2;
128 convertToRelativeCameraSpace(posXx, posYx, posZ, retVal);
129 retVal.x += (EYE_X + mTargetPosX);
130 retVal.y += (mTargetPosY);
133 public void convertToRelativeCameraSpace(float posX, float posY, float posZ, Vector3f retVal) {
136 posXx = posXx / mWidth;
137 posYx = posYx / mHeight;
139 float zDiscriminant = (mTanFovBy2 * (mTargetPosZ + EYE_Z + posZx));
140 zDiscriminant *= 2.0f;
141 float yRange = zDiscriminant;
142 float xRange = zDiscriminant * mAspectRatio;
143 posXx = ((posXx * xRange));
144 posYx = ((posYx * yRange));
149 public float getDistanceToFitRect(float f, float g) {
150 final float thisAspectRatio = (float) f / (float) g;
152 if (thisAspectRatio > mAspectRatio) {
153 // The width will hit the screen.
154 h = (f * mHeight) / mWidth;
156 // To fit ITEM_HEIGHT pixels perfectly, the targetZ value must be the 1.0f for the given fov
157 // Thus to fit h pixels,
159 float targetZ = h / mTanFovBy2;
160 targetZ = targetZ * 0.5f;
161 return -(EYE_Z - targetZ);
164 public void moveXTo(float posX) {
168 public void moveYTo(float posY) {
172 public void moveZTo(float posZ) {
176 public void moveTo(float posX, float posY, float posZ) {
177 float delta = posX - mTargetPosX;
178 float maxDelta = mWidth * 2.0f * mOneByScale;
179 delta = FloatUtils.clamp(delta, -maxDelta, maxDelta);
180 mTargetPosX += delta;
185 public void moveBy(float posX, float posY, float posZ) {
186 moveTo(posX + mTargetPosX, posY + mTargetPosY, posZ + mTargetPosZ);
189 public void commitMove() {
195 public void commitMoveInX() {
199 public void commitMoveInY() {
203 public void commitMoveInZ() {
207 public boolean computeConstraints(boolean applyConstraints, boolean applyOverflowFeedback, Vector3f firstSlotPosition,
208 Vector3f lastSlotPosition) {
209 boolean retVal = false;
210 float minX = (firstSlotPosition.x) * (1.0f / mItemHeight);
211 float maxX = (lastSlotPosition.x) * (1.0f / mItemHeight);
212 if (mTargetPosX < minX) {
213 mAmountExceeding += mTargetPosX - minX;
216 if (applyConstraints) {
221 if (mTargetPosX > maxX) {
222 mAmountExceeding += mTargetPosX - maxX;
225 if (applyConstraints) {
231 float scrollingFromEdgeX = 0.0f;
232 if (mAmountExceeding < 0.0f) {
233 scrollingFromEdgeX = mTargetPosX - minX;
235 scrollingFromEdgeX = maxX - mTargetPosX;
237 if (scrollingFromEdgeX > 0.1f) {
238 mAmountExceeding = 0.0f;
241 if (applyConstraints) {
242 mEyeEdgeOffsetX = 0.0f;
243 // We look at amount exceeding and calculate target position in the reverse direction.
244 final float maxBounceBack = 0.8f;
245 if (mAmountExceeding < -maxBounceBack)
246 mAmountExceeding = -maxBounceBack;
247 if (mAmountExceeding > maxBounceBack)
248 mAmountExceeding = maxBounceBack;
249 mTargetPosX -= mAmountExceeding * 0.8f;
250 if (mTargetPosX > maxX)
252 if (mTargetPosX < minX)
254 mAmountExceeding = 0.0f;
256 float amountExceedingToUse = mAmountExceeding;
257 final float maxThreshold = 0.6f;
258 if (amountExceedingToUse > maxThreshold)
259 amountExceedingToUse = maxThreshold;
260 if (amountExceedingToUse < -maxThreshold)
261 amountExceedingToUse = -maxThreshold;
262 if (applyOverflowFeedback)
263 mEyeEdgeOffsetX = -10.0f * amountExceedingToUse;
265 mEyeEdgeOffsetX = 0.0f;
270 public void stopMovement() {
276 public void stopMovementInX() {
280 public void stopMovementInY() {
284 public void stopMovementInZ() {
288 public boolean isAnimating() {
289 return (mPosX != mTargetPosX || mPosY != mTargetPosY || mPosZ != mTargetPosZ || mEyeOffsetAnimX != mEyeOffsetX || mEyeEdgeOffsetXAnim != mEyeEdgeOffsetX);
292 public boolean isZAnimating() {
293 return mPosZ != mTargetPosZ;
296 public void update(float timeElapsed) {
297 float factor = mConvergenceSpeed;
298 timeElapsed = (timeElapsed * factor);
299 mPosX = FloatUtils.animate(mPosX, mTargetPosX, timeElapsed);
300 mPosY = FloatUtils.animate(mPosY, mTargetPosY, timeElapsed);
301 mPosZ = FloatUtils.animate(mPosZ, mTargetPosZ, timeElapsed);
302 if (mEyeZ != EYE_Z) {
306 mEyeOffsetAnimX = FloatUtils.animate(mEyeOffsetAnimX, mEyeOffsetX, timeElapsed);
307 mEyeOffsetAnimY = FloatUtils.animate(mEyeOffsetAnimY, mEyeOffsetY, timeElapsed);
308 mEyeEdgeOffsetXAnim = FloatUtils.animate(mEyeEdgeOffsetXAnim, mEyeEdgeOffsetX, timeElapsed);
309 mTargetEyeX = EYE_X + mPosX;
310 if (mEyeZ == EYE_Z) {
312 // Enable the line below for achieving tilt while you scroll the wall.
313 // FloatUtils.animate(eyeX_, targetEyeX_, timeElapsedx - (timeElapsedx * 0.35f));
317 mEyeX += (mEyeOffsetAnimX + mEyeEdgeOffsetXAnim);
318 mLookAtX = EYE_X + mPosX;
319 mEyeY = EYE_Y + mPosY;
320 mLookAtY = EYE_Y + mPosY;
321 mEyeZ = EYE_Z + mPosZ;