public final class

GridCamera

extends Object
/*
 * Copyright (C) 2009 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.cooliris.media;

import android.os.Bundle;

public final class GridCamera {
    public static final float MAX_CAMERA_SPEED = 12.0f;
    public static final float EYE_CONVERGENCE_SPEED = 3.0f;
    public static final float EYE_X = 0;
    public static final float EYE_Y = 0;
    public static final float EYE_Z = 8.0f; // Initial z distance.
    private static final float DEFAULT_PORTRAIT_ASPECT = 320.0f / 480.0f;
    private static final float DEFAULT_LANDSCAPE_ASPECT = 1.0f / DEFAULT_PORTRAIT_ASPECT;

    public float mEyeX;
    public float mEyeY;
    public float mEyeZ;
    public float mLookAtX;
    public float mLookAtY;
    public float mLookAtZ;
    public float mUpX;
    public float mUpY;
    public float mUpZ;

    // To tilt the wall.
    public float mEyeOffsetX;
    public float mEyeOffsetY;
    private float mEyeEdgeOffsetX;
    private float mEyeEdgeOffsetXAnim;
    private float mAmountExceeding;

    // Animation speed, 1.0f is normal speed.
    public float mConvergenceSpeed;

    // Camera field of view and its relation to the grid item width.
    public float mFov;
    public float mScale;
    public float mOneByScale;
    public int mWidth;
    public int mHeight;
    public int mItemHeight;
    public int mItemWidth;
    public float mAspectRatio;
    public float mDefaultAspectRatio;

    // Camera positional information.
    private float mPosX;
    private float mPosY;
    private float mPosZ;
    private float mTargetPosX;
    private float mTargetPosY;
    private float mTargetPosZ;
    private float mEyeOffsetAnimX;
    private float mEyeOffsetAnimY;
    private float mTargetEyeX;

    // Screen width and height.
    private int mWidthBy2;
    private int mHeightBy2;
    private float mTanFovBy2;
    public float mFriction;

    public GridCamera(int width, int height, int itemWidth, int itemHeight) {
        reset();
        viewportChanged(width, height, itemWidth, itemHeight);
        mConvergenceSpeed = 1.0f;
        mFriction = 0.0f;
    }

    public void onRestoreInstanceState(Bundle savedInstanceState) {
        mEyeX = savedInstanceState.getInt(new String("Camera.mEyeX")) + EYE_X;
        mTargetPosX = savedInstanceState.getFloat(new String("Camera.mTargetPosX"));
        mTargetPosY = savedInstanceState.getFloat(new String("Camera.mTargetPosY"));
        mTargetPosZ = savedInstanceState.getFloat(new String("Camera.mTargetPosZ"));
        commitMove();
    }

    public void onSaveInstanceState(Bundle outState) {
        outState.putFloat(new String("Camera.mEyeX"), mEyeX - EYE_X);
        outState.putFloat(new String("Camera.mTargetPosX"), mTargetPosX);
        outState.putFloat(new String("Camera.mTargetPosY"), mTargetPosY);
        outState.putFloat(new String("Camera.mTargetPosZ"), mTargetPosZ);
    }

    public void reset() {
        mTargetEyeX = 0;
        mEyeX = EYE_X;
        mEyeY = EYE_Y;
        mEyeZ = EYE_Z;
        mLookAtX = EYE_X;
        mLookAtY = EYE_Y;
        mLookAtZ = 0;
        mUpX = 0;
        mUpY = 1.0f;
        mUpZ = 0;
        mPosX = 0;
        mPosY = 0;
        mPosZ = 0;
        mTargetPosX = 0;
        mTargetPosY = 0;
        mTargetPosZ = 0;
    }

    public void viewportChanged(int w, int h, float itemWidth, float itemHeight) {
        // For pixel precision we need to use this formula.
        /* fov = 2tan-1(qFactor/2*defaultZ) where qFactor = height/ItemHeight */
        float qFactor = h / (float) itemHeight;
        float fov = 2.0f * (float) Math.toDegrees(Math.atan2(qFactor / 2, GridCamera.EYE_Z));
        mWidth = w;
        mHeight = h;
        mWidthBy2 = w >> 1;
        mHeightBy2 = h >> 1;
        mAspectRatio = (h == 0) ? 1.0f : (float) w / (float) h;
        mDefaultAspectRatio = (w > h) ? DEFAULT_LANDSCAPE_ASPECT : DEFAULT_PORTRAIT_ASPECT;
        mTanFovBy2 = (float) Math.tan(Math.toRadians(fov * 0.5f));
        mItemHeight = (int) itemHeight;
        mItemWidth = (int) itemWidth;
        mScale = itemHeight;
        mOneByScale = 1.0f / (float) itemHeight;
        mFov = fov;
    }

    public void convertToScreenSpace(int posX, int posY, int posZ, Vector3f retVal) {
        // TODO
    }

    public void convertToCameraSpace(float posX, float posY, float posZ, Vector3f retVal) {
        float posXx = posX - mWidthBy2;
        float posYx = posY - mHeightBy2;
        convertToRelativeCameraSpace(posXx, posYx, posZ, retVal);
        retVal.x += (EYE_X + mTargetPosX);
        retVal.y += (mTargetPosY);
    }

    public void convertToRelativeCameraSpace(float posX, float posY, float posZ, Vector3f retVal) {
        float posXx = posX;
        float posYx = posY;
        posXx = posXx / mWidth;
        posYx = posYx / mHeight;
        float posZx = posZ;
        float zDiscriminant = (mTanFovBy2 * (mTargetPosZ + EYE_Z + posZx));
        zDiscriminant *= 2.0f;
        float yRange = zDiscriminant;
        float xRange = zDiscriminant * mAspectRatio;
        posXx = ((posXx * xRange));
        posYx = ((posYx * yRange));
        retVal.x = posXx;
        retVal.y = posYx;
    }

    public float getDistanceToFitRect(float f, float g) {
        final float thisAspectRatio = (float) f / (float) g;
        float h = g;
        if (thisAspectRatio > mAspectRatio) {
            // The width will hit the screen.
            h = (f * mHeight) / mWidth;
        }
        // To fit ITEM_HEIGHT pixels perfectly, the targetZ value must be the
        // 1.0f for the given fov
        // Thus to fit h pixels,
        h = h / mItemHeight;
        float targetZ = h / mTanFovBy2;
        targetZ = targetZ * 0.5f;
        return -(EYE_Z - targetZ);
    }

    public void moveXTo(float posX) {
        mTargetPosX = posX;
    }

    public void moveYTo(float posY) {
        mTargetPosY = posY;
    }

    public void moveZTo(float posZ) {
        mTargetPosZ = posZ;
    }

    public void moveTo(float posX, float posY, float posZ) {
        float delta = posX - mTargetPosX;
        float maxDelta = mWidth * 2.0f * mOneByScale;
        delta = FloatUtils.clamp(delta, -maxDelta, maxDelta);
        mTargetPosX += delta;
        mTargetPosY = posY;
        mTargetPosZ = posZ;
    }

    public void moveBy(float posX, float posY, float posZ) {
        moveTo(posX + mTargetPosX, posY + mTargetPosY, posZ + mTargetPosZ);
    }

    public void commitMove() {
        mPosX = mTargetPosX;
        mPosY = mTargetPosY;
        mPosZ = mTargetPosZ;
    }

    public void commitMoveInX() {
        mPosX = mTargetPosX;
    }

    public void commitMoveInY() {
        mPosY = mTargetPosY;
    }

    public void commitMoveInZ() {
        mPosZ = mTargetPosZ;
    }

    public boolean computeConstraints(boolean applyConstraints, boolean applyOverflowFeedback, Vector3f firstSlotPosition,
            Vector3f lastSlotPosition) {
        boolean retVal = false;
        float minX = (firstSlotPosition.x) * (1.0f / mItemHeight);
        float maxX = (lastSlotPosition.x) * (1.0f / mItemHeight);
        if (mTargetPosX < minX) {
            mAmountExceeding += mTargetPosX - minX;
            mTargetPosX = minX;
            mPosX = minX;
            if (applyConstraints) {
                mTargetPosX = minX;
                mFriction = 0.0f;
            }
            retVal = true;
        }
        if (mTargetPosX > maxX) {
            mAmountExceeding += mTargetPosX - maxX;
            mTargetPosX = maxX;
            mPosX = maxX;
            if (applyConstraints) {
                mTargetPosX = maxX;
                mFriction = 0.0f;
            }
            retVal = true;
        }
        if (!retVal) {
            float scrollingFromEdgeX = 0.0f;
            if (mAmountExceeding < 0.0f) {
                scrollingFromEdgeX = mTargetPosX - minX;
            } else {
                scrollingFromEdgeX = maxX - mTargetPosX;
            }
            if (scrollingFromEdgeX > 0.1f) {
                mAmountExceeding = 0.0f;
            }
        }
        if (applyConstraints) {
            mEyeEdgeOffsetX = 0.0f;
            // We look at amount exceeding and calculate target position in the
            // reverse direction.
            final float maxBounceBack = 0.8f;
            if (mAmountExceeding < -maxBounceBack)
                mAmountExceeding = -maxBounceBack;
            if (mAmountExceeding > maxBounceBack)
                mAmountExceeding = maxBounceBack;
            //mTargetPosX -= mAmountExceeding * 0.8f;
            if (mTargetPosX > maxX)
                mTargetPosX = maxX;
            if (mTargetPosX < minX)
                mTargetPosX = minX;
            mAmountExceeding = 0.0f;
        } else {
            float amountExceedingToUse = mAmountExceeding;
            final float maxThreshold = 0.6f;
            if (amountExceedingToUse > maxThreshold)
                amountExceedingToUse = maxThreshold;
            if (amountExceedingToUse < -maxThreshold)
                amountExceedingToUse = -maxThreshold;
            if (applyOverflowFeedback)
                mEyeEdgeOffsetX = -10.0f * amountExceedingToUse;
            else
                mEyeEdgeOffsetX = 0.0f;
        }
        return retVal;
    }

    public void stopMovement() {
        mTargetPosX = mPosX;
        mTargetPosY = mPosY;
        mTargetPosZ = mPosZ;
    }

    public void stopMovementInX() {
        mTargetPosX = mPosX;
    }

    public void stopMovementInY() {
        mTargetPosY = mPosY;
    }

    public void stopMovementInZ() {
        mTargetPosZ = mPosZ;
    }

    public boolean isAnimating() {
        return (mPosX != mTargetPosX || mPosY != mTargetPosY || mPosZ != mTargetPosZ || mEyeOffsetAnimX != mEyeOffsetX || mEyeEdgeOffsetXAnim != mEyeEdgeOffsetX);
    }

    public boolean isZAnimating() {
        return mPosZ != mTargetPosZ;
    }

    public void update(float timeElapsed) {
        float factor = mConvergenceSpeed;
        timeElapsed = (timeElapsed * factor);
        float oldPosX = mPosX;
        mPosX = FloatUtils.animate(mPosX, mTargetPosX, timeElapsed);
        float diff = mPosX - oldPosX;
        if (diff == 0)
            mFriction = 0.0f;
        mTargetPosX += (diff * mFriction);
        mPosY = FloatUtils.animate(mPosY, mTargetPosY, timeElapsed);
        mPosZ = FloatUtils.animate(mPosZ, mTargetPosZ, timeElapsed);
        if (mEyeZ != EYE_Z) {
            mEyeOffsetX = 0;
            mEyeOffsetY = 0;
        }
        mEyeOffsetAnimX = FloatUtils.animate(mEyeOffsetAnimX, mEyeOffsetX, timeElapsed);
        mEyeOffsetAnimY = FloatUtils.animate(mEyeOffsetAnimY, mEyeOffsetY, timeElapsed);
        mEyeEdgeOffsetXAnim = FloatUtils.animate(mEyeEdgeOffsetXAnim, mEyeEdgeOffsetX, timeElapsed);
        mTargetEyeX = EYE_X + mPosX;
        if (mEyeZ == EYE_Z) {
            mEyeX = mTargetEyeX;
            // Enable the line below for achieving tilt while you scroll the
            // wall.
            // FloatUtils.animate(eyeX_, targetEyeX_, timeElapsedx -
            // (timeElapsedx * 0.35f));
        } else {
            mEyeX = mTargetEyeX;
        }
        mEyeX += (mEyeOffsetAnimX + mEyeEdgeOffsetXAnim);
        mLookAtX = EYE_X + mPosX;
        mEyeY = EYE_Y + mPosY;
        mLookAtY = EYE_Y + mPosY;
        mEyeZ = EYE_Z + mPosZ;
        mLookAtZ = mPosZ;
    }
}