/*
* Copyright (C) 2016 Google Inc. All Rights Reserved.
*
* 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.google.android.apps.santatracker.map.cameraAnimations;
import android.os.Handler;
import com.google.android.apps.santatracker.data.SantaPreferences;
import com.google.android.apps.santatracker.map.SantaMarker;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.CancelableCallback;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
/**
* Camera animation that centers on Santa, then follows his position.
*
*/
class MoveAroundSanta extends SantaCamAnimation {
private static final int ANIMATION_DURATION = 10000;
private static final int ANIMATION_CATCHUP_DURATION = 5000;
private static final float MAX_ZOOM = 10f;
private static final float MIN_ZOOM = 7.8f;
private static final float MAX_TILT = 40f;
private static final float MIN_TILT = 0f;
private static final int SCROLL_FRAME_DURATION = 300;
private static final int STATE_FULL = 1;
private static final int STATE_CATCHUP = 2;
private static final int STATE_CATCHUP_IN = 3;
private static final int STATE_SCROLL = 4;
private static final int STATE_SMALL = 5;
private static final int STATE_IN_ANIMATION = 6;
// Order: Full, catchup, scroll, small, catchup, scroll
private static final int[] ORDER = new int[]{
STATE_FULL, STATE_IN_ANIMATION, STATE_CATCHUP, STATE_CATCHUP_IN, STATE_SCROLL,
/*STATE_SMALL, STATE_IN_ANIMATION, */ STATE_CATCHUP, STATE_CATCHUP_IN, STATE_SCROLL
};
private int mState = 0;
private long mScrollFrames = 0;
private CameraUpdate mAnimateCameraUpdate;
private CameraUpdate mMoveCameraUpdate;
private int mAnimationDuration = ANIMATION_DURATION;
MoveAroundSanta(Handler handler, GoogleMap map, SantaMarker santa) {
super(handler, map, santa);
reset();
}
@Override
public void reset() {
super.reset();
mState = 0;
}
private long mAnimationStart;
private float mAnimationBearingChange;
private float mInitialBearing;
void onSantaMoving(LatLng position) {
// only execute animation if not cancelled
// (required so that scroll won't be animated)
if (!mIsCancelled) {
switch (ORDER[mState]) {
case STATE_CATCHUP:
catchupAnimation();
nextState();
break;
case STATE_FULL:
fullAnimation();
nextState();
break;
case STATE_SMALL:
smallAnimation();
nextState();
break;
case STATE_CATCHUP_IN:
// ignore during catchup animation, heading does not change
break;
case STATE_IN_ANIMATION:
if (mAnimationStart > 0) {
updateHeading();
}
break;
case STATE_SCROLL:
if (mScrollFrames > SCROLL_FRAME_DURATION) {
nextState();
} else {
scrollAnimation(position);
mScrollFrames++;
}
break;
}
}
}
private void updateHeading() {
// never exceed progress, could be called with off-timings.
float p = Math.min(
((float) (System.currentTimeMillis() - mAnimationStart))
/ (float) ANIMATION_DURATION, 1f);
float b = mInitialBearing + (mAnimationBearingChange * p);
if (b < 0f) {
b += 360f;
}
mSanta.setCameraOrientation(b);
}
private void nextState() {
mState = (mState + 1) % ORDER.length;
mScrollFrames = 0;
}
private void catchupAnimation() {
LatLng position = mSanta.getFuturePosition(SantaPreferences.getCurrentTime()
+ ANIMATION_CATCHUP_DURATION);
mAnimationDuration = ANIMATION_CATCHUP_DURATION;
mAnimateCameraUpdate = CameraUpdateFactory.newLatLng(position);
executeRunnable(mThreadAnimate);
}
private void smallAnimation() {
LatLng pos = mSanta.getFuturePosition(SantaPreferences.getCurrentTime()
+ ANIMATION_DURATION);
float tilt = SantaPreferences.getRandom(MIN_TILT, MAX_TILT);
float bearing = SantaPreferences.getRandom(0f, 306f);
CameraPosition camera = new CameraPosition.Builder().target(pos)
.tilt(tilt).zoom(mMap.getCameraPosition().zoom)
.bearing(bearing).build();
saveBearing(bearing);
mAnimationDuration = ANIMATION_DURATION;
mAnimateCameraUpdate = CameraUpdateFactory.newCameraPosition(camera);
executeRunnable(mThreadAnimate);
}
private void scrollAnimation(LatLng position) {
mMoveCameraUpdate = CameraUpdateFactory.newLatLng(position);
executeRunnable(mThreadMove);
}
private boolean skipScroll = false;
private Runnable mThreadMove = new Runnable() {
public void run() {
if (mMap != null && mMoveCameraUpdate != null && !mIsCancelled && !skipScroll) {
mMap.moveCamera(mMoveCameraUpdate);
mAnimationStart = System.currentTimeMillis();
}
skipScroll = false;
}
};
private void fullAnimation() {
// get position in future so that camera is centered when camera
// animation is finished
LatLng pos = mSanta.getFuturePosition(SantaPreferences.getCurrentTime()
+ ANIMATION_DURATION);
float tilt = SantaPreferences.getRandom(MIN_TILT, MAX_TILT);
float zoom = SantaPreferences.getRandom(MIN_ZOOM, MAX_ZOOM);
float bearing = SantaPreferences.getRandom(0f, 306f);
// store animation heading changes
saveBearing(bearing);
CameraPosition camera = new CameraPosition.Builder().target(pos)
.tilt(tilt).zoom(zoom).bearing(bearing).build();
mAnimationDuration = ANIMATION_DURATION;
mAnimateCameraUpdate = CameraUpdateFactory.newCameraPosition(camera);
executeRunnable(mThreadAnimate);
}
private void saveBearing(float endBearing) {
float startBearing = mMap.getCameraPosition().bearing;
if (mInitialBearing > endBearing) {
if (startBearing - endBearing > 180) {
startBearing -= 360f;
}
} else {
if (endBearing - startBearing > 180) {
endBearing -= 360f;
}
}
mInitialBearing = startBearing;
mAnimationBearingChange = endBearing - startBearing;
}
void triggerPaddingAnimation() {
// Cancel the scroll animation
if (ORDER[mState] == STATE_SCROLL) {
nextState();
skipScroll = true;
}
}
private Runnable mThreadAnimate = new Runnable() {
public void run() {
if (mAnimateCameraUpdate != null) {
mMap.animateCamera(mAnimateCameraUpdate, mAnimationDuration,
mCancelListener);
mAnimationStart = System.currentTimeMillis();
}
}
};
private CancelableCallback mCancelListener = new GoogleMap.CancelableCallback() {
public void onFinish() {
nextState();
}
public void onCancel() {
}
};
}