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