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
17package com.replica.replicaisland;
18
19/**
20 * Manages the position of the camera based on a target game object.
21 */
22public class CameraSystem extends BaseObject {
23    private GameObject mTarget;
24    private float mShakeTime;
25    private float mShakeMagnitude;
26    private float mShakeOffsetY;
27    private Vector2 mCurrentCameraPosition;
28    private Vector2 mFocalPosition;
29    private Vector2 mPreInterpolateCameraPosition;
30    private Vector2 mTargetPosition;
31    private Vector2 mBias;
32    private float mTargetChangedTime;
33
34    private static final float X_FOLLOW_DISTANCE = 0.0f;
35    private static final float Y_UP_FOLLOW_DISTANCE = 90.0f;
36    private static final float Y_DOWN_FOLLOW_DISTANCE = 0.0f;
37
38    private static final float MAX_INTERPOLATE_TO_TARGET_DISTANCE = 300.0f;
39    private static final float INTERPOLATE_TO_TARGET_TIME = 1.0f;
40
41    private static int SHAKE_FREQUENCY = 40;
42
43    private static float BIAS_SPEED = 400.0f;
44
45    public CameraSystem() {
46        super();
47        mCurrentCameraPosition = new Vector2();
48        mFocalPosition = new Vector2();
49        mPreInterpolateCameraPosition = new Vector2();
50        mTargetPosition = new Vector2();
51        mBias = new Vector2();
52    }
53
54    @Override
55    public void reset() {
56        mTarget = null;
57        mCurrentCameraPosition.zero();
58        mShakeTime = 0.0f;
59        mShakeMagnitude = 0.0f;
60        mFocalPosition.zero();
61        mTargetChangedTime = 0.0f;
62        mPreInterpolateCameraPosition.zero();
63        mTargetPosition.zero();
64    }
65
66    void setTarget(GameObject target) {
67        if (target != null && mTarget != target) {
68            mPreInterpolateCameraPosition.set(mCurrentCameraPosition);
69            mPreInterpolateCameraPosition.subtract(target.getPosition());
70            if (mPreInterpolateCameraPosition.length2() <
71                    MAX_INTERPOLATE_TO_TARGET_DISTANCE * MAX_INTERPOLATE_TO_TARGET_DISTANCE) {
72                final TimeSystem time = sSystemRegistry.timeSystem;
73                mTargetChangedTime = time.getGameTime();
74                mPreInterpolateCameraPosition.set(mCurrentCameraPosition);
75            } else {
76            	mTargetChangedTime = 0.0f;
77                mCurrentCameraPosition.set(target.getPosition());
78            }
79        }
80
81        mTarget = target;
82
83    }
84
85    public GameObject getTarget() {
86		return mTarget;
87	}
88
89    void shake(float duration, float magnitude) {
90        mShakeTime = duration;
91        mShakeMagnitude = magnitude;
92    }
93
94    public boolean shaking() {
95        return mShakeTime > 0.0f;
96    }
97
98    @Override
99    public void update(float timeDelta, BaseObject parent) {
100
101        mShakeOffsetY = 0.0f;
102
103        if (mShakeTime > 0.0f) {
104            mShakeTime -= timeDelta;
105            mShakeOffsetY = (float) (Math.sin(mShakeTime * SHAKE_FREQUENCY) * mShakeMagnitude);
106        }
107
108        if (mTarget != null) {
109        	mTargetPosition.set(mTarget.getCenteredPositionX(), mTarget.getCenteredPositionY());
110            final Vector2 targetPosition = mTargetPosition;
111
112            if (mTargetChangedTime > 0.0f) {
113                final TimeSystem time = sSystemRegistry.timeSystem;
114                final float delta = time.getGameTime() - mTargetChangedTime;
115
116                mCurrentCameraPosition.x = Lerp.ease(mPreInterpolateCameraPosition.x,
117                        targetPosition.x, INTERPOLATE_TO_TARGET_TIME, delta);
118
119                mCurrentCameraPosition.y = Lerp.ease(mPreInterpolateCameraPosition.y,
120                        targetPosition.y, INTERPOLATE_TO_TARGET_TIME, delta);
121
122                if (delta > INTERPOLATE_TO_TARGET_TIME) {
123                    mTargetChangedTime = -1;
124                }
125            } else {
126
127            	// Only respect the bias if the target is moving.  No camera motion without
128            	// player input!
129            	if (mBias.length2() > 0.0f && mTarget.getVelocity().length2() > 1.0f) {
130                	mBias.normalize();
131                	mBias.multiply(BIAS_SPEED * timeDelta);
132                	mCurrentCameraPosition.add(mBias);
133                }
134
135                final float xDelta = targetPosition.x - mCurrentCameraPosition.x;
136                if (Math.abs(xDelta) > X_FOLLOW_DISTANCE) {
137                    mCurrentCameraPosition.x = targetPosition.x - (X_FOLLOW_DISTANCE * Utils.sign(xDelta));
138                }
139
140
141                final float yDelta = targetPosition.y - mCurrentCameraPosition.y;
142                if (yDelta > Y_UP_FOLLOW_DISTANCE) {
143                    mCurrentCameraPosition.y = targetPosition.y - Y_UP_FOLLOW_DISTANCE;
144                } else if (yDelta < -Y_DOWN_FOLLOW_DISTANCE) {
145                    mCurrentCameraPosition.y = targetPosition.y + Y_DOWN_FOLLOW_DISTANCE;
146                }
147
148            }
149
150        	mBias.zero();
151
152        }
153
154        mFocalPosition.x = (float) Math.floor(mCurrentCameraPosition.x);
155        mFocalPosition.x = snapFocalPointToWorldBoundsX(mFocalPosition.x);
156
157        mFocalPosition.y = (float) Math.floor(mCurrentCameraPosition.y + mShakeOffsetY);
158        mFocalPosition.y = snapFocalPointToWorldBoundsY(mFocalPosition.y);
159    }
160
161    /** Returns the x position of the camera's look-at point. */
162    public float getFocusPositionX() {
163        return mFocalPosition.x;
164    }
165
166    /** Returns the y position of the camera's look-at point. */
167    public float getFocusPositionY() {
168        return mFocalPosition.y;
169    }
170
171    public boolean pointVisible(Vector2 point, float radius) {
172        boolean visible = false;
173        final float width = sSystemRegistry.contextParameters.gameWidth / 2.0f;
174        final float height = sSystemRegistry.contextParameters.gameHeight / 2.0f;
175        if (Math.abs(mFocalPosition.x - point.x) < (width + radius)) {
176            if (Math.abs(mFocalPosition.y - point.y) < (height + radius)) {
177                visible = true;
178            }
179        }
180        return visible;
181    }
182
183    /** Snaps a coordinate against the bounds of the world so that it may not pass out
184     * of the visible area of the world.
185     * @param worldX An x-coordinate in world units.
186     * @return An x-coordinate that is guaranteed not to expose the edges of the world.
187     */
188    public float snapFocalPointToWorldBoundsX(float worldX) {
189        float focalPositionX = worldX;
190        final float width = sSystemRegistry.contextParameters.gameWidth;
191        final LevelSystem level = sSystemRegistry.levelSystem;
192        if (level != null) {
193            final float worldPixelWidth = Math.max(level.getLevelWidth(), width);
194            final float rightEdge = focalPositionX + (width / 2.0f);
195            final float leftEdge = focalPositionX - (width / 2.0f);
196
197            if (rightEdge > worldPixelWidth) {
198                focalPositionX = worldPixelWidth - (width / 2.0f);
199            } else if (leftEdge < 0) {
200                focalPositionX = width / 2.0f;
201            }
202        }
203        return focalPositionX;
204    }
205
206    /** Snaps a coordinate against the bounds of the world so that it may not pass out
207     * of the visible area of the world.
208     * @param worldY A y-coordinate in world units.
209     * @return A y-coordinate that is guaranteed not to expose the edges of the world.
210     */
211    public float snapFocalPointToWorldBoundsY(float worldY) {
212        float focalPositionY = worldY;
213
214        final float height = sSystemRegistry.contextParameters.gameHeight;
215        final LevelSystem level = sSystemRegistry.levelSystem;
216        if (level != null) {
217            final float worldPixelHeight = Math.max(level.getLevelHeight(), sSystemRegistry.contextParameters.gameHeight);
218            final float topEdge = focalPositionY + (height / 2.0f);
219            final float bottomEdge = focalPositionY - (height / 2.0f);
220
221            if (topEdge > worldPixelHeight) {
222                focalPositionY = worldPixelHeight - (height / 2.0f);
223            } else if (bottomEdge < 0) {
224                focalPositionY = height / 2.0f;
225            }
226        }
227
228        return focalPositionY;
229    }
230
231	public void addCameraBias(Vector2 bias) {
232		final float x = bias.x - mFocalPosition.x;
233		final float y = bias.y - mFocalPosition.y;
234		final float biasX = mBias.x;
235		final float biasY = mBias.y;
236		mBias.set(x, y);
237		mBias.normalize();
238		mBias.add(biasX, biasY);
239	}
240
241
242}
243