1 /* 2 * Copyright (C) 2009 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.cooliris.media; 18 19 import android.os.Bundle; 20 21 public final class GridCamera { 22 public static final float MAX_CAMERA_SPEED = 12.0f; 23 public static final float EYE_CONVERGENCE_SPEED = 3.0f; 24 public static final float EYE_X = 0; 25 public static final float EYE_Y = 0; 26 public static final float EYE_Z = 8.0f; // Initial z distance. 27 private static final float DEFAULT_PORTRAIT_ASPECT = 320.0f / 480.0f; 28 private static final float DEFAULT_LANDSCAPE_ASPECT = 1.0f / DEFAULT_PORTRAIT_ASPECT; 29 30 public float mEyeX; 31 public float mEyeY; 32 public float mEyeZ; 33 public float mLookAtX; 34 public float mLookAtY; 35 public float mLookAtZ; 36 public float mUpX; 37 public float mUpY; 38 public float mUpZ; 39 40 // To tilt the wall. 41 public float mEyeOffsetX; 42 public float mEyeOffsetY; 43 private float mEyeEdgeOffsetX; 44 private float mEyeEdgeOffsetXAnim; 45 private float mAmountExceeding; 46 47 // Animation speed, 1.0f is normal speed. 48 public float mConvergenceSpeed; 49 50 // Camera field of view and its relation to the grid item width. 51 public float mFov; 52 public float mScale; 53 public float mOneByScale; 54 public int mWidth; 55 public int mHeight; 56 public int mItemHeight; 57 public int mItemWidth; 58 public float mAspectRatio; 59 public float mDefaultAspectRatio; 60 61 // Camera positional information. 62 private float mPosX; 63 private float mPosY; 64 private float mPosZ; 65 private float mTargetPosX; 66 private float mTargetPosY; 67 private float mTargetPosZ; 68 private float mEyeOffsetAnimX; 69 private float mEyeOffsetAnimY; 70 private float mTargetEyeX; 71 72 // Screen width and height. 73 private int mWidthBy2; 74 private int mHeightBy2; 75 private float mTanFovBy2; 76 public float mFriction; 77 78 public GridCamera(int width, int height, int itemWidth, int itemHeight) { 79 reset(); 80 viewportChanged(width, height, itemWidth, itemHeight); 81 mConvergenceSpeed = 1.0f; 82 mFriction = 0.0f; 83 } 84 85 public void onRestoreInstanceState(Bundle savedInstanceState) { 86 mEyeX = savedInstanceState.getInt(new String("Camera.mEyeX")) + EYE_X; 87 mTargetPosX = savedInstanceState.getFloat(new String("Camera.mTargetPosX")); 88 mTargetPosY = savedInstanceState.getFloat(new String("Camera.mTargetPosY")); 89 mTargetPosZ = savedInstanceState.getFloat(new String("Camera.mTargetPosZ")); 90 commitMove(); 91 } 92 93 public void onSaveInstanceState(Bundle outState) { 94 outState.putFloat(new String("Camera.mEyeX"), mEyeX - EYE_X); 95 outState.putFloat(new String("Camera.mTargetPosX"), mTargetPosX); 96 outState.putFloat(new String("Camera.mTargetPosY"), mTargetPosY); 97 outState.putFloat(new String("Camera.mTargetPosZ"), mTargetPosZ); 98 } 99 100 public void reset() { 101 mTargetEyeX = 0; 102 mEyeX = EYE_X; 103 mEyeY = EYE_Y; 104 mEyeZ = EYE_Z; 105 mLookAtX = EYE_X; 106 mLookAtY = EYE_Y; 107 mLookAtZ = 0; 108 mUpX = 0; 109 mUpY = 1.0f; 110 mUpZ = 0; 111 mPosX = 0; 112 mPosY = 0; 113 mPosZ = 0; 114 mTargetPosX = 0; 115 mTargetPosY = 0; 116 mTargetPosZ = 0; 117 } 118 119 public void viewportChanged(int w, int h, float itemWidth, float itemHeight) { 120 // For pixel precision we need to use this formula. 121 /* fov = 2tan-1(qFactor/2*defaultZ) where qFactor = height/ItemHeight */ 122 float qFactor = h / (float) itemHeight; 123 float fov = 2.0f * (float) Math.toDegrees(Math.atan2(qFactor / 2, GridCamera.EYE_Z)); 124 mWidth = w; 125 mHeight = h; 126 mWidthBy2 = w >> 1; 127 mHeightBy2 = h >> 1; 128 mAspectRatio = (h == 0) ? 1.0f : (float) w / (float) h; 129 mDefaultAspectRatio = (w > h) ? DEFAULT_LANDSCAPE_ASPECT : DEFAULT_PORTRAIT_ASPECT; 130 mTanFovBy2 = (float) Math.tan(Math.toRadians(fov * 0.5f)); 131 mItemHeight = (int) itemHeight; 132 mItemWidth = (int) itemWidth; 133 mScale = itemHeight; 134 mOneByScale = 1.0f / (float) itemHeight; 135 mFov = fov; 136 } 137 138 public void convertToScreenSpace(int posX, int posY, int posZ, Vector3f retVal) { 139 // TODO 140 } 141 142 public void convertToCameraSpace(float posX, float posY, float posZ, Vector3f retVal) { 143 float posXx = posX - mWidthBy2; 144 float posYx = posY - mHeightBy2; 145 convertToRelativeCameraSpace(posXx, posYx, posZ, retVal); 146 retVal.x += (EYE_X + mTargetPosX); 147 retVal.y += (mTargetPosY); 148 } 149 150 public void convertToRelativeCameraSpace(float posX, float posY, float posZ, Vector3f retVal) { 151 float posXx = posX; 152 float posYx = posY; 153 posXx = posXx / mWidth; 154 posYx = posYx / mHeight; 155 float posZx = posZ; 156 float zDiscriminant = (mTanFovBy2 * (mTargetPosZ + EYE_Z + posZx)); 157 zDiscriminant *= 2.0f; 158 float yRange = zDiscriminant; 159 float xRange = zDiscriminant * mAspectRatio; 160 posXx = ((posXx * xRange)); 161 posYx = ((posYx * yRange)); 162 retVal.x = posXx; 163 retVal.y = posYx; 164 } 165 166 public float getDistanceToFitRect(float f, float g) { 167 final float thisAspectRatio = (float) f / (float) g; 168 float h = g; 169 if (thisAspectRatio > mAspectRatio) { 170 // The width will hit the screen. 171 h = (f * mHeight) / mWidth; 172 } 173 // To fit ITEM_HEIGHT pixels perfectly, the targetZ value must be the 174 // 1.0f for the given fov 175 // Thus to fit h pixels, 176 h = h / mItemHeight; 177 float targetZ = h / mTanFovBy2; 178 targetZ = targetZ * 0.5f; 179 return -(EYE_Z - targetZ); 180 } 181 182 public void moveXTo(float posX) { 183 mTargetPosX = posX; 184 } 185 186 public void moveYTo(float posY) { 187 mTargetPosY = posY; 188 } 189 190 public void moveZTo(float posZ) { 191 mTargetPosZ = posZ; 192 } 193 194 public void moveTo(float posX, float posY, float posZ) { 195 float delta = posX - mTargetPosX; 196 float maxDelta = mWidth * 2.0f * mOneByScale; 197 delta = FloatUtils.clamp(delta, -maxDelta, maxDelta); 198 mTargetPosX += delta; 199 mTargetPosY = posY; 200 mTargetPosZ = posZ; 201 } 202 203 public void moveBy(float posX, float posY, float posZ) { 204 moveTo(posX + mTargetPosX, posY + mTargetPosY, posZ + mTargetPosZ); 205 } 206 207 public void commitMove() { 208 mPosX = mTargetPosX; 209 mPosY = mTargetPosY; 210 mPosZ = mTargetPosZ; 211 } 212 213 public void commitMoveInX() { 214 mPosX = mTargetPosX; 215 } 216 217 public void commitMoveInY() { 218 mPosY = mTargetPosY; 219 } 220 221 public void commitMoveInZ() { 222 mPosZ = mTargetPosZ; 223 } 224 225 public boolean computeConstraints(boolean applyConstraints, boolean applyOverflowFeedback, Vector3f firstSlotPosition, 226 Vector3f lastSlotPosition) { 227 boolean retVal = false; 228 float minX = (firstSlotPosition.x) * (1.0f / mItemHeight); 229 float maxX = (lastSlotPosition.x) * (1.0f / mItemHeight); 230 if (mTargetPosX < minX) { 231 mAmountExceeding += mTargetPosX - minX; 232 mTargetPosX = minX; 233 mPosX = minX; 234 if (applyConstraints) { 235 mTargetPosX = minX; 236 mFriction = 0.0f; 237 } 238 retVal = true; 239 } 240 if (mTargetPosX > maxX) { 241 mAmountExceeding += mTargetPosX - maxX; 242 mTargetPosX = maxX; 243 mPosX = maxX; 244 if (applyConstraints) { 245 mTargetPosX = maxX; 246 mFriction = 0.0f; 247 } 248 retVal = true; 249 } 250 if (!retVal) { 251 float scrollingFromEdgeX = 0.0f; 252 if (mAmountExceeding < 0.0f) { 253 scrollingFromEdgeX = mTargetPosX - minX; 254 } else { 255 scrollingFromEdgeX = maxX - mTargetPosX; 256 } 257 if (scrollingFromEdgeX > 0.1f) { 258 mAmountExceeding = 0.0f; 259 } 260 } 261 if (applyConstraints) { 262 mEyeEdgeOffsetX = 0.0f; 263 // We look at amount exceeding and calculate target position in the 264 // reverse direction. 265 final float maxBounceBack = 0.8f; 266 if (mAmountExceeding < -maxBounceBack) 267 mAmountExceeding = -maxBounceBack; 268 if (mAmountExceeding > maxBounceBack) 269 mAmountExceeding = maxBounceBack; 270 //mTargetPosX -= mAmountExceeding * 0.8f; 271 if (mTargetPosX > maxX) 272 mTargetPosX = maxX; 273 if (mTargetPosX < minX) 274 mTargetPosX = minX; 275 mAmountExceeding = 0.0f; 276 } else { 277 float amountExceedingToUse = mAmountExceeding; 278 final float maxThreshold = 0.6f; 279 if (amountExceedingToUse > maxThreshold) 280 amountExceedingToUse = maxThreshold; 281 if (amountExceedingToUse < -maxThreshold) 282 amountExceedingToUse = -maxThreshold; 283 if (applyOverflowFeedback) 284 mEyeEdgeOffsetX = -10.0f * amountExceedingToUse; 285 else 286 mEyeEdgeOffsetX = 0.0f; 287 } 288 return retVal; 289 } 290 291 public void stopMovement() { 292 mTargetPosX = mPosX; 293 mTargetPosY = mPosY; 294 mTargetPosZ = mPosZ; 295 } 296 297 public void stopMovementInX() { 298 mTargetPosX = mPosX; 299 } 300 301 public void stopMovementInY() { 302 mTargetPosY = mPosY; 303 } 304 305 public void stopMovementInZ() { 306 mTargetPosZ = mPosZ; 307 } 308 309 public boolean isAnimating() { 310 return (mPosX != mTargetPosX || mPosY != mTargetPosY || mPosZ != mTargetPosZ || mEyeOffsetAnimX != mEyeOffsetX || mEyeEdgeOffsetXAnim != mEyeEdgeOffsetX); 311 } 312 313 public boolean isZAnimating() { 314 return mPosZ != mTargetPosZ; 315 } 316 317 public void update(float timeElapsed) { 318 float factor = mConvergenceSpeed; 319 timeElapsed = (timeElapsed * factor); 320 float oldPosX = mPosX; 321 mPosX = FloatUtils.animate(mPosX, mTargetPosX, timeElapsed); 322 float diff = mPosX - oldPosX; 323 if (diff == 0) 324 mFriction = 0.0f; 325 mTargetPosX += (diff * mFriction); 326 mPosY = FloatUtils.animate(mPosY, mTargetPosY, timeElapsed); 327 mPosZ = FloatUtils.animate(mPosZ, mTargetPosZ, timeElapsed); 328 if (mEyeZ != EYE_Z) { 329 mEyeOffsetX = 0; 330 mEyeOffsetY = 0; 331 } 332 mEyeOffsetAnimX = FloatUtils.animate(mEyeOffsetAnimX, mEyeOffsetX, timeElapsed); 333 mEyeOffsetAnimY = FloatUtils.animate(mEyeOffsetAnimY, mEyeOffsetY, timeElapsed); 334 mEyeEdgeOffsetXAnim = FloatUtils.animate(mEyeEdgeOffsetXAnim, mEyeEdgeOffsetX, timeElapsed); 335 mTargetEyeX = EYE_X + mPosX; 336 if (mEyeZ == EYE_Z) { 337 mEyeX = mTargetEyeX; 338 // Enable the line below for achieving tilt while you scroll the 339 // wall. 340 // FloatUtils.animate(eyeX_, targetEyeX_, timeElapsedx - 341 // (timeElapsedx * 0.35f)); 342 } else { 343 mEyeX = mTargetEyeX; 344 } 345 mEyeX += (mEyeOffsetAnimX + mEyeEdgeOffsetXAnim); 346 mLookAtX = EYE_X + mPosX; 347 mEyeY = EYE_Y + mPosY; 348 mLookAtY = EYE_Y + mPosY; 349 mEyeZ = EYE_Z + mPosZ; 350 mLookAtZ = mPosZ; 351 } 352 } 353