/***********************************************************************
* mt4j Copyright (c) 2008 - 2009 C.Ruff, Fraunhofer-Gesellschaft All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
***********************************************************************/
package org.mt4j.input.inputProcessors.componentProcessors.zoomProcessor;
import java.util.ArrayList;
import java.util.List;
import org.mt4j.components.interfaces.IMTComponent3D;
import org.mt4j.input.inputData.InputCursor;
import org.mt4j.input.inputData.MTFingerInputEvt;
import org.mt4j.input.inputProcessors.IInputProcessor;
import org.mt4j.input.inputProcessors.MTGestureEvent;
import org.mt4j.input.inputProcessors.componentProcessors.AbstractComponentProcessor;
import org.mt4j.input.inputProcessors.componentProcessors.AbstractCursorProcessor;
import org.mt4j.util.math.Vector3D;
import processing.core.PApplet;
/**
* The Class ZoomProcessor.
* Multitouch background zoom gesture. This will change the scene's CAMERA.
* Fires ZoomEvent gesture events.
* <br><strong>NOTE:</strong> Should be only used in combination with a MTCanvas component.
* @author Christopher Ruff
*/
public class ZoomProcessor extends AbstractCursorProcessor {
/** The zoom detect radius. */
private float zoomDetectRadius;
/** The old distance. */
private float oldDistance;
/** The applet. */
private PApplet applet;
/** The un used motions. */
private List<InputCursor> unUsedMotions;
/** The locked motions. */
private List<InputCursor> lockedMotions;
/**
* Instantiates a new zoom processor.
*
* @param graphicsContext the graphics context
*/
public ZoomProcessor(PApplet graphicsContext){
this(graphicsContext, graphicsContext.width/2);
}
/**
* Instantiates a new zoom processor.
*
* @param graphicsContext the graphics context
* @param zoomDetectRadius the zoom detect radius
*/
public ZoomProcessor(PApplet graphicsContext, float zoomDetectRadius){
this.applet = graphicsContext;
this.unUsedMotions = new ArrayList<InputCursor>();
this.lockedMotions = new ArrayList<InputCursor>();
this.zoomDetectRadius = zoomDetectRadius;
this.setLockPriority(2);
}
@Override
public void cursorStarted(InputCursor m, MTFingerInputEvt positionEvent) {
IMTComponent3D comp = positionEvent.getTargetComponent();
if (lockedMotions.size() >= 2){ //scale with 2 fingers already in progress
unUsedMotions.add(m);
logger.debug(this.getName() + " has already enough motions for this gesture - adding to unused ID:" + m.getId());
}else{ //no scale in progress yet
if (unUsedMotions.size() == 1){
logger.debug(this.getName() + " has already has 1 unused motion - we can try start gesture! used with ID:" + unUsedMotions.get(0).getId() + " and new motion ID:" + m.getId());
InputCursor otherMotion = unUsedMotions.get(0);
//See if we can obtain a lock on both motions
if (this.canLock(otherMotion, m)){
float newDistance = Vector3D.distance(
new Vector3D(otherMotion.getCurrentEvent().getPosX(), otherMotion.getCurrentEvent().getPosY(),0),
new Vector3D(m.getCurrentEvent().getPosX(), m.getCurrentEvent().getPosY(),0));
if (newDistance < zoomDetectRadius) {
this.oldDistance = newDistance;
this.getLock(otherMotion, m);
lockedMotions.add(otherMotion);
lockedMotions.add(m);
unUsedMotions.remove(otherMotion);
logger.debug(this.getName() + " we could lock both motions! And fingers in zoom distance!");
this.fireGestureEvent(new ZoomEvent(this, MTGestureEvent.GESTURE_DETECTED, comp, m, otherMotion, 0f, comp.getViewingCamera() ));
}else{
logger.debug(this.getName() + " Motions not close enough to start gesture. Distance: " + newDistance);
}
}else{
logger.debug(this.getName() + " we could NOT lock both motions!");
unUsedMotions.add(m);
}
}else{
logger.debug(this.getName() + " we didnt have a unused motion previously to start gesture now");
unUsedMotions.add(m);
}
}
}
@Override
public void cursorUpdated(InputCursor m, MTFingerInputEvt positionEvent) {
IMTComponent3D comp = positionEvent.getTargetComponent();
if (lockedMotions.size() == 2 && lockedMotions.contains(m)){
InputCursor firstMotion = lockedMotions.get(0);
InputCursor secondMotion = lockedMotions.get(1);
float fingerDistance = Vector3D.distance(
new Vector3D(firstMotion.getCurrentEvent().getPosX(), firstMotion.getCurrentEvent().getPosY(), 0),
new Vector3D(secondMotion.getCurrentEvent().getPosX(), secondMotion.getCurrentEvent().getPosY(), 0));
float camZoomAmount = fingerDistance - oldDistance;
oldDistance = fingerDistance;
if (m.equals(firstMotion)){
this.fireGestureEvent(new ZoomEvent(this, MTGestureEvent.GESTURE_UPDATED, comp, firstMotion, secondMotion, camZoomAmount, comp.getViewingCamera()));
}else{
this.fireGestureEvent(new ZoomEvent(this, MTGestureEvent.GESTURE_UPDATED, comp, firstMotion, secondMotion, camZoomAmount, comp.getViewingCamera()));
}
}
}
@Override
public void cursorEnded(InputCursor m, MTFingerInputEvt positionEvent) {
IMTComponent3D comp = positionEvent.getTargetComponent();
logger.debug(this.getName() + " INPUT_ENDED RECIEVED - MOTION: " + m.getId());
if (lockedMotions.size() == 2 && lockedMotions.contains(m)){
InputCursor leftOverMotion = (lockedMotions.get(0).equals(m))? lockedMotions.get(1) : lockedMotions.get(0);
lockedMotions.remove(m);
if (unUsedMotions.size() > 0){ //Check if there are other motions we could use for scaling if one was removed
InputCursor futureMotion = unUsedMotions.get(0);
if (this.canLock(futureMotion)){ //check if we have priority to lock another motion and use it
float newDistance = Vector3D.distance(
new Vector3D(leftOverMotion.getCurrentEvent().getPosX(), leftOverMotion.getCurrentEvent().getPosY(),0),
new Vector3D(futureMotion.getCurrentEvent().getPosX(), futureMotion.getCurrentEvent().getPosY(),0));
if (newDistance < zoomDetectRadius) {//Check if other motion is in distance
this.oldDistance = newDistance;
this.getLock(futureMotion);
unUsedMotions.remove(futureMotion);
lockedMotions.add(futureMotion);
logger.debug(this.getName() + " we could lock another motion! (ID:" + futureMotion.getId() +")");
logger.debug(this.getName() + " continue with different motions (ID: " + futureMotion.getId() + ")" + " " + "(ID: " + leftOverMotion.getId() + ")");
//TODO fire start evt?
}else{
this.endGesture(m, leftOverMotion, comp);
}
}else{ //we dont have permission to use other motion - End scale
this.endGesture(m, leftOverMotion, comp);
}
}else{ //no more unused motions on comp - End scale
this.endGesture(m, leftOverMotion, comp);
}
this.unLock(m); //FIXME TEST
}else{ //motion was not a scaling involved motion
if (unUsedMotions.contains(m)){
unUsedMotions.remove(m);
}
}
}
/**
* End gesture.
*
* @param inputEndedMotion the input ended motion
* @param leftOverMotion the left over motion
* @param comp the comp
*/
private void endGesture(InputCursor inputEndedMotion, InputCursor leftOverMotion, IMTComponent3D comp){
lockedMotions.clear();
unUsedMotions.add(leftOverMotion);
this.unLock(leftOverMotion);
this.fireGestureEvent(new ZoomEvent(this, MTGestureEvent.GESTURE_ENDED, comp, inputEndedMotion, leftOverMotion, 0f, comp.getViewingCamera()));
}
/* (non-Javadoc)
* @see org.mt4j.input.inputAnalyzers.IInputAnalyzer#motionLocked(org.mt4j.input.inputData.InputMotion, org.mt4j.input.inputAnalyzers.IInputAnalyzer)
*/
@Override
public void cursorLocked(InputCursor m, IInputProcessor lockingAnalyzer) {
if (lockingAnalyzer instanceof AbstractComponentProcessor){
logger.debug(this.getName() + " Recieved MOTION LOCKED by (" + ((AbstractComponentProcessor)lockingAnalyzer).getName() + ") - motion ID: " + m.getId());
}else{
logger.debug(this.getName() + " Recieved MOTION LOCKED by higher priority signal - motion ID: " + m.getId());
}
if (lockedMotions.contains(m)){ //motions was used here! -> we have to stop the gesture
//put all used motions in the unused motion list and clear the usedmotionlist
unUsedMotions.addAll(lockedMotions);
lockedMotions.clear();
//TODO fire ended evt?
logger.debug(this.getName() + " motion:" + m.getId() + " MOTION LOCKED. Was an active motion in this gesture - we therefor have to stop this gesture!");
}
}
/* (non-Javadoc)
* @see org.mt4j.input.inputAnalyzers.IInputAnalyzer#motionUnlocked(org.mt4j.input.inputData.InputMotion)
*/
@Override
public void cursorUnlocked(InputCursor m) {
logger.debug(this.getName() + " Recieved UNLOCKED signal for motion ID: " + m.getId());
if (lockedMotions.size() >= 2){ //we dont need the unlocked motion, gesture still in progress
return;
}
if (unUsedMotions.contains(m)){ //should always be true here!?
if (unUsedMotions.size() >= 2){ //we can try to resume the gesture
InputCursor firstMotion = unUsedMotions.get(0);
InputCursor secondMotion = unUsedMotions.get(1);
//See if we can obtain a lock on both motions
if (this.canLock(firstMotion, secondMotion)){
float newDistance = Vector3D.distance(
new Vector3D(firstMotion.getCurrentEvent().getPosX(), firstMotion.getCurrentEvent().getPosY(),0),
new Vector3D(secondMotion.getCurrentEvent().getPosX(), secondMotion.getCurrentEvent().getPosY(),0));
if (newDistance < zoomDetectRadius) {//Check if other motion is in distance
this.oldDistance = newDistance;
this.getLock(firstMotion, secondMotion);
unUsedMotions.remove(firstMotion);
unUsedMotions.remove(secondMotion);
lockedMotions.add(firstMotion);
lockedMotions.add(secondMotion);
logger.debug(this.getName() + " we could lock motions: " + firstMotion.getId() +", " + secondMotion.getId());
logger.debug(this.getName() + " continue with different motions (ID: " + firstMotion.getId() + ")" + " " + "(ID: " + secondMotion.getId() + ")");
//TODO fire started evt?
}else{
logger.debug(this.getName() + " distance was too great between motions: " + firstMotion.getId() +", " + secondMotion.getId() + " distance: " + newDistance);
}
}else{
logger.debug(this.getName() + " we could NOT lock motions: " + firstMotion.getId() +", " + secondMotion.getId());
}
}
}else{
logger.error(this.getName() + "hmmm - investigate why is motion not in unusedList?");
}
}
/* (non-Javadoc)
* @see org.mt4j.input.inputAnalyzers.componentAnalyzers.AbstractComponentInputAnalyzer#getName()
*/
@Override
public String getName() {
return "Zoom Processor";
}
}