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.replica.replicaisland;
18 
19 /**
20  * Manages the position of the camera based on a target game object.
21  */
22 public 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 
CameraSystem()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
reset()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 
setTarget(GameObject target)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 
getTarget()85     public GameObject getTarget() {
86 		return mTarget;
87 	}
88 
shake(float duration, float magnitude)89     void shake(float duration, float magnitude) {
90         mShakeTime = duration;
91         mShakeMagnitude = magnitude;
92     }
93 
shaking()94     public boolean shaking() {
95         return mShakeTime > 0.0f;
96     }
97 
98     @Override
update(float timeDelta, BaseObject parent)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. */
getFocusPositionX()162     public float getFocusPositionX() {
163         return mFocalPosition.x;
164     }
165 
166     /** Returns the y position of the camera's look-at point. */
getFocusPositionY()167     public float getFocusPositionY() {
168         return mFocalPosition.y;
169     }
170 
pointVisible(Vector2 point, float radius)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      */
snapFocalPointToWorldBoundsX(float worldX)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      */
snapFocalPointToWorldBoundsY(float worldY)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 
addCameraBias(Vector2 bias)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