CameraAnimator.java

/*
 * Copyright © 2012 Nokia Corporation. All rights reserved.
 * Nokia and Nokia Connecting People are registered trademarks of Nokia Corporation.
 * Oracle and Java are trademarks or registered trademarks of Oracle and/or its
 * affiliates. Other product and company names mentioned herein may be trademarks
 * or trade names of their respective owners.
 * See LICENSE.TXT for license information.
 */

package com.nokia.example.amaze.ui;

import javax.microedition.m3g.Camera;
import javax.microedition.m3g.Transform;
import javax.microedition.m3g.World;

/**
 * Class for pause and transition camera animations.
 */
public class CameraAnimator {
	// Constants
	private static final float NUM_OF_TRANSITION_STEPS = 30;
	private static final float MIN_TRANSITION_STEP = 0.01f;
	private static final float PAUSE_ANIMATION_ROTATION_SPEED = 0.2f;
	
	// Animation types
	public static final int PAUSE_ANIMATION = 0;
	public static final int TRANSITION_ANIMATION_TO_POV = 1;
	public static final int TRANSITION_ANIMATION_TO_TOP = 2;
	public static final int LEVEL_RESET_ANIMATION_STEP = 3;
	
	// Members
	private Listener _listener = null;
	private World _world = null;
	private Camera _camera = null;
	private Transform _currentTransform = null;
	private Transform _targetTransform = null;
	private float[] _currentMatrix = null;
	private float[] _targetMatrix = null;
	private float[] _transitionMatrix = null;
	private float[] _currentOrientation = null;
	private float[] _targetOrientation = null;
	private int _animationType = -1;
	private int _stepCount = 0;
	private boolean _running = false;
	
	/**
	 * Constructor.
	 * @param listener The listener which is the maze canvas.
	 */
	public CameraAnimator(Listener listener, World world, Camera camera) {
		if (listener == null || world == null || camera == null) {
			throw new IllegalArgumentException("None of the arguments can be null!");
		}
		
		_listener = listener;
		_world = world;
		_camera = camera;
	}
	
	/**
	 * Starts the animation of the given type.
	 * @param type The animation type.
	 * @param targetTransform The target transform. Can be null.
	 */
	public void startAnimation(int type, Transform targetTransform, float[] targetOrientation) {
		System.out.println("CameraAnimator::startAnimation(): " + type);
		
		if (_running) {
			releaseResources();		
			_running = false;			
		}
		
		_animationType = type;
		_stepCount = 0;
		
		if (targetTransform != null) {
			_targetTransform = targetTransform;
			_targetMatrix = new float[16];
			_targetTransform.get(_targetMatrix);
		} 
				
		_currentTransform = new Transform();
		Camera activeCamera = _world.getActiveCamera();
		
		if (activeCamera == null) {
			// No active camera set, do set it now
			System.out.println("CameraAnimator::startAnimation(): No active camera set.");
			_world.setActiveCamera(_camera);
			_camera.getTransform(_currentTransform);		}
		else {
			activeCamera.getTransform(_currentTransform);
			
			if (_camera != activeCamera) {
				System.out.println("CameraAnimator::startAnimation(): Switching the camera.");
				_camera.setTransform(_currentTransform);
				_world.setActiveCamera(_camera);
			}
		}		
		
		if (targetOrientation != null) {
			_targetOrientation = targetOrientation;
			_currentOrientation = new float[4];
			_camera.getOrientation(_currentOrientation);			
		}
		
		if (targetTransform != null) {
			_currentMatrix = new float[16];
			_currentTransform.get(_currentMatrix);
			
			_transitionMatrix = calculateTransitionMatrix(_currentMatrix,
														  _targetMatrix);
		}
		
		_running = true;		
	}
	
	/**
	 * For convenience.
	 * @param type
	 * @param targetCamera
	 */
	public void startAnimation(int type, Camera targetCamera) {
		if (targetCamera == null) {
			return;
		}
		
		Transform transform = new Transform();
		targetCamera.getTransform(transform);
		float[] orientation = new float[4];
		targetCamera.getOrientation(orientation);
		startAnimation(type, transform, orientation);
	}
	
	/**
	 * For convenience.
	 * @param type
	 */
	public void startAnimation(int type) {
		startAnimation(type, null, null);
	}
	
	/**
	 * Stops the animation.
	 */
	public void stopAnimation() {
		System.out.println("CameraAnimator::stopAnimation()");
		releaseResources();		
		_running = false;
		_listener.onAnimationFinished(_animationType);
	}
	
	/** 
	 * @return True if the animator running, false otherwise.
	 */
	public final boolean running() {
		return _running;
	}
	
	/** 
	 * @return The type of the current animation.
	 */
	public final int animationType() {
		return _animationType;
	}
	
	/**
	 * 
	 */
	public void update() {
		if (_targetTransform != null) {
			if (!takeTransitionStepTowardsTarget()) {
				System.out.println("CameraAnimator::update(): No more transition steps required.");
				
				if (_animationType == TRANSITION_ANIMATION_TO_POV
						 || _animationType == TRANSITION_ANIMATION_TO_TOP)
				{
					// We're done here
					_running = false;
					stopAnimation();
				}
				else {
					releaseResources();
				}
			}
			
			if (_currentTransform != null) {
				_camera.setTransform(_currentTransform);
			}
			
			if (_targetOrientation != null) {
				_camera.setOrientation(_currentOrientation[0],
									   _currentOrientation[1],
									   _currentOrientation[2],
									   _currentOrientation[3]);
			}
		}
		else if (_animationType == PAUSE_ANIMATION) {
			_camera.postRotate(PAUSE_ANIMATION_ROTATION_SPEED, 0f, 1f, 0f);
		}
		else if (_animationType == LEVEL_RESET_ANIMATION_STEP) {
			_camera.translate(0f, 12.0f, 12.0f);
			_stepCount++;
			
			if (_stepCount > 60) {
				stopAnimation();
			}
		}
		else {
			stopAnimation();
		}
	}

	/**
	 * Prints the matrix of the given transform. For debugging purposes.
	 * @param transform The transform to print.
	 */
	public static final void printTransform(Transform transform) {
		if (transform == null) {
			return;
		}
		
		final float[] matrix = new float[16];
		transform.get(matrix);
		
		for (int i = 0; i < 16; ++i) {
			if ((i + 1) % 4 == 0) {
				System.out.println(matrix[i]);
			}
			else {
				System.out.print(matrix[i] + "\t");
			}
		}
	}
	
	/**
	 * Prints the values of the properties of the given camera.
	 * @param camera The camera of which values to print.
	 */
	public static final void printCameraValues(Camera camera) {
		if (camera != null) {
			Transform transform = new Transform();
			camera.getTransform(transform);
			printTransform(transform);
			
			// Print orientation
			final float[] orientation = new float[4];
			camera.getOrientation(orientation);
			System.out.print("Orientation: [");
			System.out.println(orientation[0] + ", "
							   + orientation[1] + ", "
							   + orientation[2] + ", "
							   + orientation[3] + "]");			
		}
	}
	
	/**
	 * 
	 * @param current
	 * @param target
	 * @param diff
	 * @return
	 */
	private final float newValue(float current, float target, float diff) {
		if (diff < 0) {
			diff = Math.abs(diff);
		}
		
		if (Math.abs(current - target) < diff) {
			return target;
		}

		if (target > current) {
			return current + diff;
		}
		
		return current - diff;
	}
	
	/**
	 * Takes a step towards the target camera transition.
	 * @return True if the matrix was modified, false otherwise.
	 */
	private boolean takeTransitionStepTowardsTarget() {
		final int matrixLength = _currentMatrix.length;
		final int maxChangesCount = (_targetOrientation == null)
				? matrixLength : _transitionMatrix.length;
		int noChangesCount = 0; 

		for (int i = 0; i < matrixLength; ++i) {			
			if (_currentMatrix[i] == _targetMatrix[i]) {
				noChangesCount++;
				continue;
			}
			
			_currentMatrix[i] = newValue(_currentMatrix[i],
					_targetMatrix[i], _transitionMatrix[i]); 
		}
		
		if (_targetOrientation != null) {					
			for (int i = 0; i < 4; ++i) {
				if (_currentOrientation[i] == _targetOrientation[i]) {
					noChangesCount++;
					continue;
				}
			
				_currentOrientation[i] =
						newValue(_currentOrientation[i],
								 _targetOrientation[i],
								 _transitionMatrix[i + matrixLength]);
			}
		}
		
		_currentTransform.set(_currentMatrix);
		/*System.out.println("CameraAnimator::takeTransitionStepTowardsTarget(): "
				+ noChangesCount + "/" + maxChangesCount);*/
		return (noChangesCount != maxChangesCount);
	}
	
	/**
	 * 
	 * @param from
	 * @param to
	 * @return
	 */
	private float[] calculateTransitionMatrix(float[] from, float[] to) {
		if (from == null || to == null || from.length != to.length){
			return null;
		}
		
		final int length = from.length;		
		float[] transitionMatrix = new float[length + 4];
		
		for (int i = 0; i < length; ++i) {
			if (to[i] == from[i]) {
				continue;
			}
			
			transitionMatrix[i] = (to[i] - from[i]) / NUM_OF_TRANSITION_STEPS;
			/*System.out.println("(" + to[i] + " - " + from[i] + ") / "
					+ NUM_OF_TRANSITION_STEPS + " = " + transitionMatrix[i]);*/
			
			if (transitionMatrix[i] == 0) {
				transitionMatrix[i] = MIN_TRANSITION_STEP;	
			}
		}
		
		if (_targetOrientation != null) {		
			// Calculate the transition steps for the orientation
			for (int i = 0; i < 4; ++i) {
				transitionMatrix[i + length] =
						(_targetOrientation[i] - _currentOrientation[i])
						/ NUM_OF_TRANSITION_STEPS;
				
				if (transitionMatrix[i + length] == 0) {
					transitionMatrix[i + length] = MIN_TRANSITION_STEP;	
				}				
			}
		}
		
		return transitionMatrix;
	}
	
	/**
	 * Releases resources.
	 */
	private void releaseResources() {
		_currentTransform = null;
		_targetTransform = null;
		_targetMatrix = null;
		_currentMatrix = null;
		_currentOrientation = null;
		_targetOrientation = null;
		_transitionMatrix = null;		
	}
	
	/**
	 * Listener interface for events of this class.
	 */
	public interface Listener {
		/**
		 * Called when an animation is finished.
		 * @param animationType The type of the animation which was finished.
		 */
		void onAnimationFinished(int animationType);
	}	
}