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