/***********************************************************************
* 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.panProcessor;
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.Tools3D;
import org.mt4j.util.math.ToolsGeometry;
import org.mt4j.util.math.Vector3D;
import processing.core.PApplet;
/**
* The Class PanProcessorTwoFingers. Multitouch gesture processor for panning the
* canvas by moving the scene's camera. Should only be registered with MTCanvas components.
* Fires PanEvent gesture events.
* <br><strong>NOTE:</strong> Should be only used in combination with a MTCanvas component.
* @author Christopher Ruff
*/
public class PanProcessorTwoFingers extends AbstractCursorProcessor {
/** The detect radius. */
private float detectRadius;
/** The applet. */
private PApplet applet;
/** The un used cursors. */
private List<InputCursor> unUsedCursors;
/** The locked cursors. */
private List<InputCursor> lockedCursors;
/** The point in plane. */
private Vector3D pointInPlane;
/** The plane normal. */
private Vector3D planeNormal;
/**
* Instantiates a new pan processor two fingers.
*
* @param app the app
*/
public PanProcessorTwoFingers(PApplet app) {
this(app, app.width/2);
}
/**
* Instantiates a new pan processor two fingers.
*
* @param applet the applet
* @param panDetectRadius the pan detect radius
*/
public PanProcessorTwoFingers(PApplet applet, float panDetectRadius){
this.applet = applet;
this.detectRadius = panDetectRadius;
this.unUsedCursors = new ArrayList<InputCursor>();
this.lockedCursors = new ArrayList<InputCursor>();
this.pointInPlane = new Vector3D(0,0,0);
this.planeNormal = new Vector3D(0,0,1);
this.setLockPriority(2);
}
@Override
public void cursorStarted(InputCursor m, MTFingerInputEvt positionEvent) {
if (lockedCursors.size() >= 2){ //gesture with 2 fingers already in progress
unUsedCursors.add(m);
logger.debug(this.getName() + " has already enough cursors for this gesture - adding to unused ID:" + m.getId());
}else{ //no gesture in progress yet
if (unUsedCursors.size() == 1){
logger.debug(this.getName() + " has already has 1 unused cursor - we can try start gesture! used with ID:" + unUsedCursors.get(0).getId() + " and new cursor ID:" + m.getId());
InputCursor otherCursor = unUsedCursors.get(0);
//See if we can obtain a lock on both cursors
if (this.canLock(otherCursor, m)){
float newDistance = Vector3D.distance(
new Vector3D(otherCursor.getCurrentEvent().getPosX(), otherCursor.getCurrentEvent().getPosY(),0),
new Vector3D(m.getCurrentEvent().getPosX(), m.getCurrentEvent().getPosY(),0));
if (newDistance < detectRadius) {
this.getLock(otherCursor, m);
logger.debug(this.getName() + " we could lock both cursors! And fingers in distance! - " + newDistance);
unUsedCursors.remove(otherCursor);
lockedCursors.add(otherCursor);
lockedCursors.add(m);
this.fireGestureEvent(new PanTwoFingerEvent(this, MTGestureEvent.GESTURE_DETECTED, positionEvent.getTargetComponent(), otherCursor, m, new Vector3D(0,0,0), positionEvent.getTargetComponent().getViewingCamera()));
}else{
logger.debug(this.getName() + " Cursors not close enough to start gesture. Distance: " + newDistance);
}
}else{
logger.debug(this.getName() + " we could NOT lock both cursors!");
unUsedCursors.add(m);
}
}else{
logger.debug(this.getName() + " we didnt have a unused cursor previously to start gesture now");
unUsedCursors.add(m);
}
}
}
@Override
public void cursorUpdated(InputCursor m, MTFingerInputEvt positionEvent) {
if (lockedCursors.size() == 2 && lockedCursors.contains(m)){
InputCursor firstCursor = lockedCursors.get(0);
InputCursor secondCursor = lockedCursors.get(1);
Vector3D distance = (m.equals(firstCursor))? getNewTranslation(positionEvent.getTargetComponent(), firstCursor, secondCursor) : getNewTranslation(positionEvent.getTargetComponent(), secondCursor, firstCursor);
// //logger.debug("DIST: " + distance);
this.fireGestureEvent(new PanTwoFingerEvent(this, MTGestureEvent.GESTURE_UPDATED, positionEvent.getTargetComponent(), firstCursor, secondCursor, new Vector3D(distance.getX(),distance.getY(),0), positionEvent.getTargetComponent().getViewingCamera()));
}
}
@Override
public void cursorEnded(InputCursor m, MTFingerInputEvt positionEvent) {
IMTComponent3D comp = positionEvent.getTargetComponent();
logger.debug(this.getName() + " INPUT_ENDED RECIEVED - MOTION: " + m.getId());
if (lockedCursors.size() == 2 && lockedCursors.contains(m)){
InputCursor leftOverCursor = (lockedCursors.get(0).equals(m))? lockedCursors.get(1) : lockedCursors.get(0);
lockedCursors.remove(m);
if (unUsedCursors.size() > 0){ //Check if there are other cursors we could use for scaling if one was removed
InputCursor futureCursor = unUsedCursors.get(0);
if (this.canLock(futureCursor)){ //check if we have priority to lock another cursor and use it
float newDistance = Vector3D.distance(
new Vector3D(leftOverCursor.getCurrentEvent().getPosX(), leftOverCursor.getCurrentEvent().getPosY(),0),
new Vector3D(futureCursor.getCurrentEvent().getPosX(), futureCursor.getCurrentEvent().getPosY(),0));
if (newDistance < detectRadius) {//Check if other cursor is in distance
this.getLock(futureCursor);
unUsedCursors.remove(futureCursor);
lockedCursors.add(futureCursor);
logger.debug(this.getName() + " we could lock another cursor! (ID:" + futureCursor.getId() +")");
logger.debug(this.getName() + " continue with different cursors (ID: " + futureCursor.getId() + ")" + " " + "(ID: " + leftOverCursor.getId() + ")");
//TODO fire start evt?
}else{
this.endGesture(m, leftOverCursor, comp);
}
}else{ //we dont have permission to use other cursor - End scale
this.endGesture(m, leftOverCursor, comp);
}
}else{ //no more unused cursors on comp - End scale
this.endGesture(m, leftOverCursor, comp);
}
this.unLock(m); //FIXME TEST
}else{ //cursor was not a scaling involved cursor
if (unUsedCursors.contains(m)){
unUsedCursors.remove(m);
}
}
}
/**
* End gesture.
*
* @param inputEndedCursor the input ended cursor
* @param leftOverCursor the left over cursor
* @param comp the comp
*/
private void endGesture(InputCursor inputEndedCursor, InputCursor leftOverCursor, IMTComponent3D comp){
lockedCursors.clear();
unUsedCursors.add(leftOverCursor);
this.unLock(leftOverCursor);
this.fireGestureEvent(new PanTwoFingerEvent(this, MTGestureEvent.GESTURE_ENDED, comp, inputEndedCursor, leftOverCursor, new Vector3D(0,0,0), comp.getViewingCamera()));
}
/* (non-Javadoc)
* @see org.mt4j.input.inputAnalyzers.IInputAnalyzer#cursorLocked(org.mt4j.input.inputData.InputCursor, 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() + ") - cursor ID: " + m.getId());
}else{
logger.debug(this.getName() + " Recieved MOTION LOCKED by higher priority signal - cursor ID: " + m.getId());
}
if (lockedCursors.contains(m)){ //cursors was used here! -> we have to stop the gesture
//put all used cursors in the unused cursor list and clear the usedcursorlist
unUsedCursors.addAll(lockedCursors);
lockedCursors.clear();
//TODO fire ended evt?
logger.debug(this.getName() + " cursor:" + m.getId() + " MOTION LOCKED. Was an active cursor in this gesture!");
}
}
/* (non-Javadoc)
* @see org.mt4j.input.inputAnalyzers.IInputAnalyzer#cursorUnlocked(org.mt4j.input.inputData.InputCursor)
*/
@Override
public void cursorUnlocked(InputCursor m) {
logger.debug(this.getName() + " Recieved UNLOCKED signal for cursor ID: " + m.getId());
if (lockedCursors.size() >= 2){ //we dont need the unlocked cursor, gesture still in progress
return;
}
if (unUsedCursors.contains(m)){ //should always be true here!?
if (unUsedCursors.size() >= 2){ //we can try to resume the gesture
InputCursor firstCursor = unUsedCursors.get(0);
InputCursor secondCursor = unUsedCursors.get(1);
//See if we can obtain a lock on both cursors
if (this.canLock(firstCursor, secondCursor)){
float newDistance = Vector3D.distance(
new Vector3D(firstCursor.getCurrentEvent().getPosX(), firstCursor.getCurrentEvent().getPosY(),0),
new Vector3D(secondCursor.getCurrentEvent().getPosX(), secondCursor.getCurrentEvent().getPosY(),0));
if (newDistance < detectRadius) {//Check if other cursor is in distance
this.getLock(firstCursor, secondCursor);
unUsedCursors.remove(firstCursor);
unUsedCursors.remove(secondCursor);
lockedCursors.add(firstCursor);
lockedCursors.add(secondCursor);
logger.debug(this.getName() + " we could lock cursors: " + firstCursor.getId() +", " + secondCursor.getId());
logger.debug(this.getName() + " continue with different cursors (ID: " + firstCursor.getId() + ")" + " " + "(ID: " + secondCursor.getId() + ")");
//TODO fire start evt?
}else{
logger.debug(this.getName() + " distance was too great between cursors: " + firstCursor.getId() +", " + secondCursor.getId() + " distance: " + newDistance);
}
}else{
logger.debug(this.getName() + " we could NOT lock cursors: " + firstCursor.getId() +", " + secondCursor.getId());
}
}
}else{
logger.error(this.getName() + "hmmm - investigate why is cursor not in unusedList?");
}
}
/**
* Gets the new translation.
*
* @param comp the comp
* @param movingCursor the moving cursor
* @param otherCursor the other cursor
*
* @return the new translation
*/
private Vector3D getNewTranslation(IMTComponent3D comp, InputCursor movingCursor, InputCursor otherCursor){
Vector3D fromFirstFinger = ToolsGeometry.getRayPlaneIntersection(
Tools3D.getCameraPickRay(applet, comp.getViewingCamera(), movingCursor.getPreviousEvent().getPosX(), movingCursor.getPreviousEvent().getPosY()),
planeNormal,
pointInPlane);
Vector3D fromSecondFinger = ToolsGeometry.getRayPlaneIntersection(
Tools3D.getCameraPickRay(applet, comp.getViewingCamera(), otherCursor.getCurrentEvent().getPosX(), otherCursor.getCurrentEvent().getPosY()),
planeNormal,
pointInPlane);
Vector3D oldMiddlePoint = getMiddlePointBetweenFingers(fromSecondFinger, fromFirstFinger);
Vector3D toFirstFinger = ToolsGeometry.getRayPlaneIntersection(
Tools3D.getCameraPickRay(applet, comp.getViewingCamera(), movingCursor.getCurrentEvent().getPosX(), movingCursor.getCurrentEvent().getPosY()),
planeNormal,
pointInPlane);
Vector3D newMiddlePoint = getMiddlePointBetweenFingers(toFirstFinger , fromSecondFinger);
Vector3D distance = newMiddlePoint.getSubtracted(oldMiddlePoint);
return distance;
}
/**
* Gets the middle point between fingers.
*
* @param firstFinger the first finger
* @param secondFinger the second finger
*
* @return the middle point between fingers
*/
private Vector3D getMiddlePointBetweenFingers(Vector3D firstFinger, Vector3D secondFinger){
Vector3D bla = secondFinger.getSubtracted(firstFinger); //= Richtungsvektor vom 1. zum 2. finger
bla.scaleLocal(0.5f); //take the half
return (new Vector3D(firstFinger.getX() + bla.getX(), firstFinger.getY() + bla.getY(), firstFinger.getZ() + bla.getZ()));
}
/* (non-Javadoc)
* @see org.mt4j.input.inputAnalyzers.componentAnalyzers.AbstractComponentInputAnalyzer#getName()
*/
@Override
public String getName() {
return "two finger pan detector";
}
}