/***********************************************************************
* 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.rotateProcessor;
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 RotateProcessor. Rotation multitouch gesture.
* Fires RotateEvent gesture events.
* @author Christopher Ruff
*/
public class RotateProcessor extends AbstractCursorProcessor {
/** The applet. */
private PApplet applet;
// /** The un used cursors. */
// private List<InputCursor> unUsedCursors;
//
// /** The locked cursors. */
// private List<InputCursor> lockedCursors;
/** The rc. */
private RotationContext rc;
/** The drag plane normal. */
private Vector3D dragPlaneNormal;
/**
* Instantiates a new rotate processor.
*
* @param graphicsContext the graphics context
*/
public RotateProcessor(PApplet graphicsContext){
this.applet = graphicsContext;
// this.unUsedCursors = new ArrayList<InputCursor>();
// this.lockedCursors = new ArrayList<InputCursor>();
this.dragPlaneNormal = new Vector3D(0,0,1);
this.setLockPriority(2);
}
////////////////////////////
/*
public InputCursor getFarthestComponentCursorTo(InputCursor c, AbstractCursorProcessor acp){
return getFarthestComponentCursorTo(c, acp.getActiveComponentCursorsArray());
}
//TODO also check if we could lock it !? -> else try to get other -> make method getNearestAvailableCursor
public InputCursor getFarthestComponentCursorTo(InputCursor c, InputCursor[] allCursors){
float currDist = Float.MIN_VALUE;
InputCursor fartherstCursor = null;
Vector3D cursorPos = c.getPosition();
for (InputCursor currentCursor : allCursors) {
if (currentCursor.equals(c))
continue;
float distanceToCurrentCursor = currentCursor.getPosition().distance2D(cursorPos);
if (distanceToCurrentCursor >= currDist){
currDist = distanceToCurrentCursor;
fartherstCursor = currentCursor;
}
}
return fartherstCursor;
}
*/
/////////////////////
@Override
public void cursorStarted(InputCursor newCursor, MTFingerInputEvt positionEvent) {
IMTComponent3D comp = positionEvent.getTargetComponent();
logger.debug(this.getName() + " INPUT_STARTED, Cursor: " + newCursor.getId());
List<InputCursor> alreadyLockedCursors = getLockedCursors();
if (alreadyLockedCursors.size() >= 2){ //gesture with 2 fingers already in progress
//TODO get the 2 cursors which are most far away from each other
InputCursor firstCursor = alreadyLockedCursors.get(0);
InputCursor secondCursor = alreadyLockedCursors.get(1);
if (isCursorDistanceGreater(firstCursor, secondCursor, newCursor) && canLock(firstCursor, newCursor)){
RotationContext newContext = new RotationContext(firstCursor, newCursor, comp);
if (!newContext.isGestureAborted()){ //Check if we could start gesture (ie. if fingers on component)
rc = newContext;
//Lock new Second cursor
this.getLock(firstCursor, newCursor);
this.unLock(secondCursor);
logger.debug(this.getName() + " we could lock cursors: " + firstCursor.getId() +", and more far away cursor " + newCursor.getId());
}else{
logger.debug(this.getName() + " we could NOT exchange new second cursor - cursor not on component: " + firstCursor.getId() +", " + secondCursor.getId());
}
}else{
logger.debug(this.getName() + " has already enough cursors for this gesture and the new cursors distance to the first one ist greater (or we dont have the priority to lock it) - adding to unused ID:" + newCursor.getId());
}
}else{ //no gesture in progress yet
List<InputCursor> availableCursors = getFreeComponentCursors();
logger.debug(this.getName() + " Available cursors: " + availableCursors.size());
if (availableCursors.size() >= 2){
InputCursor otherCursor = getFarthestFreeComponentCursorTo(newCursor);
// logger.debug(this.getName() + " already had 1 unused cursor - we can try start gesture! used Cursor ID:" + otherCursor.getId() + " and new cursor ID:" + newCursor.getId());
if (this.canLock(otherCursor, newCursor)){ //TODO remove check, since alreday checked in getAvailableComponentCursors()?
rc = new RotationContext(otherCursor, newCursor, comp);
if (!rc.isGestureAborted()){
this.getLock(otherCursor, newCursor);
logger.debug(this.getName() + " we could lock both cursors!");
this.fireGestureEvent(new RotateEvent(this, MTGestureEvent.GESTURE_DETECTED, comp, otherCursor, newCursor, Vector3D.ZERO_VECTOR, rc.getRotationPoint(), 0f));
}else{
logger.debug(this.getName() + " gesture aborted, probably at least 1 finger not on component!");
rc = null;
}
}else{
logger.debug(this.getName() + " we could NOT lock both cursors!");
}
}else{
logger.debug(this.getName() + " still missing a cursor to start gesture");
}
}
}
@Override
public void cursorUpdated(InputCursor m, MTFingerInputEvt positionEvent) {
IMTComponent3D comp = positionEvent.getTargetComponent();
// if (lockedCursors.size() == 2 && lockedCursors.contains(m)){
List<InputCursor> alreadyLockedCursors = getLockedCursors();
if (rc != null && alreadyLockedCursors.size() == 2 && alreadyLockedCursors.contains(m)){
float rotationAngleDegrees = rc.updateAndGetRotationAngle(m);
this.fireGestureEvent(new RotateEvent(this, MTGestureEvent.GESTURE_UPDATED, comp, rc.getPinFingerCursor(), rc.getRotateFingerCursor(), Vector3D.ZERO_VECTOR, rc.getRotationPoint(), rotationAngleDegrees));
}
}
@Override
public void cursorEnded(InputCursor c, MTFingerInputEvt positionEvent) {
IMTComponent3D comp = positionEvent.getTargetComponent();
logger.debug(this.getName() + " INPUT_ENDED RECIEVED - CURSOR: " + c.getId());
logger.debug("Rotate ended -> Active cursors: " + getCurrentComponentCursors().size() + " Available cursors: " + getFreeComponentCursors().size() + " Locked cursors: " + getLockedCursors().size());
if (getLockedCursors().contains(c)){
InputCursor firstCursor = rc.getFirstCursor();
InputCursor secondCursor = rc.getSecondCursor();
if (firstCursor.equals(c) || secondCursor.equals(c)){ //The leaving cursor was used by the processor
InputCursor leftOverCursor = firstCursor.equals(c) ? secondCursor : firstCursor;
InputCursor futureCursor = getFarthestFreeCursorTo(leftOverCursor, getCurrentComponentCursorsArray());
if (futureCursor != null){ //already checked in getFartherstAvailableCursor() if we can lock it
RotationContext newContext = new RotationContext(futureCursor, leftOverCursor, comp);
if (!newContext.isGestureAborted()){
rc = newContext;
this.getLock(leftOverCursor, futureCursor);
logger.debug(this.getName() + " continue with different cursors (ID: " + futureCursor.getId() + ")" + " " + "(ID: " + leftOverCursor.getId() + ")");
}else{ //couldnt start gesture - cursor's not on component
this.endGesture(leftOverCursor, comp, firstCursor, secondCursor);
}
}else{ //we cant use another cursor - End gesture
this.endGesture(leftOverCursor, comp, firstCursor, secondCursor);
}
this.unLock(c);
}
}
}
private void endGesture(InputCursor leftOverCursor, IMTComponent3D component, InputCursor firstCursor, InputCursor secondCursor){
this.unLock(leftOverCursor);
this.fireGestureEvent(new RotateEvent(this, MTGestureEvent.GESTURE_ENDED, component, firstCursor, secondCursor, Vector3D.ZERO_VECTOR, rc.getRotationPoint(), 0));
this.rc = null;
}
//TODO evtl bei finger_UP doch unlock() auf alle used cursors damit geringere cursors nochmal �bernehmen k�nnen und dann wenn sie
//den fingerUP evt kriegen auch wirklich gestureENDED senden k�nnen, sonst wirds evtl nie gesended wenn nicht in usedCursors bei
//finger_UP
//TODO oder eben doch bei locked steal ended senden und bei resume wieder start!? oder gilt oberes trotzdem?
//suppose theres a priority 3 gesture
//TODO at resuming scale check if cursor still on object! else we get problems and a wrong startPoint etc
/* (non-Javadoc)
* @see org.mt4j.input.inputAnalyzers.IInputAnalyzer#cursorLocked(org.mt4j.input.inputData.InputCursor, org.mt4j.input.inputAnalyzers.IInputAnalyzer)
*/
@Override
public void cursorLocked(InputCursor c, IInputProcessor lockingAnalyzer) {
if (lockingAnalyzer instanceof AbstractComponentProcessor){
logger.debug(this.getName() + " Recieved CURSOR LOCKED by (" + ((AbstractComponentProcessor)lockingAnalyzer).getName() + ") - cursor ID: " + c.getId());
}else{
logger.debug(this.getName() + " Recieved CURSOR LOCKED by higher priority signal - cursor ID: " + c.getId());
}
//FIXME do nothing now? since locked stuff is done in getLockedCursors() method? -> check in cursorUpated() method if cursor is in getLockedCzrsir list instead of lockedCursor list!
//cursors are already unlocked from the processor in the InputCursor class
if (rc != null && (rc.getFirstCursor().equals(c) || rc.getSecondCursor().equals(c))){
//TODO do we have to unlock the 2nd cursor, besides "c" ??
this.unLockAllCursors();
rc = null;
logger.debug(this.getName() + " cursor:" + c.getId() + " CURSOR 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 (getLockedCursors().size() >= 2){ //we dont need the unlocked cursor, gesture still in progress
return;
}
//Dont we have to unlock the cursors if there could still 1 be locked?
//-> actually we always lock 2 cursors at once so should never be only 1 locked, but just for safety..
this.unLockAllCursors();
List<InputCursor> availableCursors = getFreeComponentCursors();
if (availableCursors.size() >= 2){ //we can try to resume the gesture
InputCursor firstCursor = availableCursors.get(0);
InputCursor secondCursor = getFarthestFreeComponentCursorTo(firstCursor);
//See if we can obtain a lock on both cursors
IMTComponent3D comp = firstCursor.getFirstEvent().getTargetComponent();
RotationContext newContext = new RotationContext(firstCursor, secondCursor, comp);
if (!newContext.isGestureAborted()){ //Check if we could start gesture (ie. if fingers on component)
rc = newContext;
this.getLock(firstCursor, secondCursor);
logger.debug(this.getName() + " we could lock cursors: " + firstCursor.getId() +", " + secondCursor.getId());
}else{
rc = null;
logger.debug(this.getName() + " we could NOT resume gesture - cursors not on component: " + firstCursor.getId() +", " + secondCursor.getId());
}
}
}
/**
* The Class RotationContext.
*/
private class RotationContext {
/** The pin finger start. */
private Vector3D pinFingerStart;
/** The pin finger last. */
private Vector3D pinFingerLast;
/** The pin finger new. */
private Vector3D pinFingerNew;
/** The rotate finger start. */
private Vector3D rotateFingerStart;
/** The rotate finger last. */
private Vector3D rotateFingerLast;
/** The rotate finger new. */
private Vector3D rotateFingerNew;
/** The last rotation vect. */
private Vector3D lastRotationVect;
/** The object. */
private IMTComponent3D object;
/** The rotation point. */
private Vector3D rotationPoint;
/** The pin finger cursor. */
private InputCursor pinFingerCursor;
/** The rotate finger cursor. */
private InputCursor rotateFingerCursor;
/** The new finger middle pos. */
private Vector3D newFingerMiddlePos;
/** The old finger middle pos. */
private Vector3D oldFingerMiddlePos;
/** The pin finger translation vect. */
private Vector3D pinFingerTranslationVect;
private boolean gestureAborted;
/**
* Instantiates a new rotation context.
*
* @param pinFingerCursor the pin finger cursor
* @param rotateFingerCursor the rotate finger cursor
* @param object the object
*/
public RotationContext(InputCursor pinFingerCursor, InputCursor rotateFingerCursor, IMTComponent3D object){
this.pinFingerCursor = pinFingerCursor;
this.rotateFingerCursor = rotateFingerCursor;
Vector3D interPoint = getIntersection(applet, object, pinFingerCursor);
if (interPoint !=null)
pinFingerNew = interPoint;
else{
logger.warn(getName() + " Pinfinger NEW = NULL");
pinFingerNew = new Vector3D();
gestureAborted = true;
}
//Use lastEvent when resuming with another cursor that started long ago
Vector3D interPointRot = getIntersection(applet, object, rotateFingerCursor);
if (interPointRot !=null)
rotateFingerStart = interPointRot;
else{
logger.warn(getName() + " rotateFingerStart = NULL");
rotateFingerStart = new Vector3D();
//TODO ABORT THE Rotation HERE!
gestureAborted = true;
}
this.pinFingerStart = pinFingerNew.getCopy();
this.pinFingerLast = pinFingerStart.getCopy();
this.rotateFingerLast = rotateFingerStart.getCopy();
this.rotateFingerNew = rotateFingerStart.getCopy();
this.object = object;
this.rotationPoint = pinFingerNew.getCopy();
//Get the rotation vector for reference for the next rotation
this.lastRotationVect = rotateFingerStart.getSubtracted(pinFingerNew);
newFingerMiddlePos = getMiddlePointBetweenFingers();
oldFingerMiddlePos = newFingerMiddlePos.getCopy();
pinFingerTranslationVect = new Vector3D(0,0,0);
//FIXME REMOVE!
// dragPlaneNormal = ((MTPolygon)object).getNormal();
// logger.debug("DragNormal: " + dragPlaneNormal);
}
/**
* Update and get rotation angle.
*
* @param moveCursor the move cursor
*
* @return the float
*/
public float updateAndGetRotationAngle(InputCursor moveCursor) {
// /*
float newAngleRad;
float newAngleDegrees;
//save the current pinfinger location as the old one
this.pinFingerLast = this.pinFingerNew;
//save the current pinfinger location as the old one
this.rotateFingerLast = this.rotateFingerNew;
//Check which finger moved and has to be updated
if (moveCursor.equals(pinFingerCursor)){
updatePinFinger();
}
else if (moveCursor.equals(rotateFingerCursor)){
updateRotateFinger();
}
//FIXME REMOVE!
// dragPlaneNormal = ((MTPolygon)object).getNormal();
// logger.debug("DragNormal: " + dragPlaneNormal);
//TODO drop Z values after that?
//calculate the vector between the rotation finger vectors
// Vector3D currentRotationVect = rotateFingerNew.getSubtracted(pinFingerNew);
Vector3D currentRotationVect = rotateFingerNew.getSubtracted(pinFingerNew).normalizeLocal(); //FIXME TEST normalize rotation vector
//calculate the angle between the rotaion finger vectors
newAngleRad = Vector3D.angleBetween(lastRotationVect, currentRotationVect);
newAngleDegrees = (float)Math.toDegrees(newAngleRad);
//FIXME EXPERIMENTAL BECAUSE ANGLEBETWEEN GIVES ROTATIONS SOMETIMES WHEN BOTH VECTORS ARE EQUAL!?
if (rotateFingerLast.equalsVector(rotateFingerNew) && pinFingerLast.equalsVector(pinFingerNew)){
//logger.debug("Angle gleich lassen");
newAngleDegrees = 0.0f;
}else{
//logger.debug("Neuer Angle: " + newAngleDegrees);
}
// logger.debug("lastRotVect: " + lastRotationVect + " currentROtationVect: " + currentRotationVect + " Deg: " + newAngleDegrees);
Vector3D cross = lastRotationVect.getCross(currentRotationVect);
//Get the direction of rotation
if (cross.getZ() < 0){
newAngleDegrees*=-1;
}
//Check if the current and last rotation vectors are equal or not
if (!Float.isNaN(newAngleDegrees)/*!String.valueOf(newAngleDegrees).equalsIgnoreCase("NaN")*/){
//if (newAngleDegrees != Float.NaN){ //if the vectors are equal rotationangle is NAN?
lastRotationVect = currentRotationVect;
return newAngleDegrees;
}else{
//lastRotationVect = currentRotationVect;
return 0;
}
// */
// return 0;
}
/**
* Update rotate finger.
*/
private void updateRotateFinger(){
if (object == null || object.getViewingCamera() == null){ //IF component was destroyed while gesture still active
this.gestureAborted = true;
return ;
}
//TODO save last position and use that one if new one is null.. everywhere!
Vector3D newRotateFingerPos = ToolsGeometry.getRayPlaneIntersection(Tools3D.getCameraPickRay(applet, object, rotateFingerCursor),
dragPlaneNormal,
rotateFingerStart.getCopy());
//Update the field
if (newRotateFingerPos != null){
this.rotateFingerNew = newRotateFingerPos;
//Reset pinfinger tranlation vector
this.pinFingerTranslationVect = new Vector3D(0,0,0);
this.rotationPoint = pinFingerNew;
}else{
logger.error(getName() + " new newRotateFinger Pos = null at update");
}
}
/**
* Update pin finger.
*/
private void updatePinFinger(){
if (object == null){ //IF component was destroyed while gesture still active
this.gestureAborted = true;
return;
}
Vector3D newPinFingerPos = ToolsGeometry.getRayPlaneIntersection(
Tools3D.getCameraPickRay(applet, object, pinFingerCursor),
dragPlaneNormal,
pinFingerStart.getCopy());
if (newPinFingerPos != null){
// Update pinfinger with new position
this.pinFingerNew = newPinFingerPos;
this.pinFingerTranslationVect = pinFingerNew.getSubtracted(pinFingerLast); //FIXME REMOVE?
//Set the Rotation finger as the rotation point because the pinfinger was moved
this.rotationPoint = rotateFingerNew; //FIXME REMOVE!!? made because of scale
}else{
logger.error(getName() + " new PinFinger Pos = null at update");
}
}
//if obj is drag enabled, und not scalable! send middlepoint delta f�r translate,
/**
* Gets the updated middle finger pos delta.
*
* @return the updated middle finger pos delta
*/
public Vector3D getUpdatedMiddleFingerPosDelta(){
newFingerMiddlePos = getMiddlePointBetweenFingers();
Vector3D returnVect = newFingerMiddlePos.getSubtracted(oldFingerMiddlePos);
this.oldFingerMiddlePos = newFingerMiddlePos;
return returnVect;
}
/**
* Gets the middle point between fingers.
*
* @return the middle point between fingers
*/
public Vector3D getMiddlePointBetweenFingers(){
Vector3D bla = rotateFingerNew.getSubtracted(pinFingerNew); //= Richtungsvektor vom 1. zum 2. finger
bla.scaleLocal(0.5f); //take the half
return (new Vector3D(pinFingerNew.getX() + bla.getX(), pinFingerNew.getY() + bla.getY(), pinFingerNew.getZ() + bla.getZ()));
}
/**
* Gets the pin finger translation vect.
*
* @return the pin finger translation vect
*/
public Vector3D getPinFingerTranslationVect() {
return pinFingerTranslationVect;
}
/**
* Gets the pin finger start.
*
* @return the pin finger start
*/
public Vector3D getPinFingerStart() {
return pinFingerStart;
}
/**
* Gets the rotate finger start.
*
* @return the rotate finger start
*/
public Vector3D getRotateFingerStart() {
return rotateFingerStart;
}
/**
* Gets the rotation point.
*
* @return the rotation point
*/
public Vector3D getRotationPoint() {
return rotationPoint;
}
/**
* Gets the pin finger cursor.
*
* @return the pin finger cursor
*/
public InputCursor getPinFingerCursor() {
return pinFingerCursor;
}
/**
* Gets the rotate finger cursor.
*
* @return the rotate finger cursor
*/
public InputCursor getRotateFingerCursor() {
return rotateFingerCursor;
}
public boolean isGestureAborted() {
return gestureAborted;
}
public InputCursor getFirstCursor(){
return this.pinFingerCursor;
}
public InputCursor getSecondCursor(){
return this.rotateFingerCursor;
}
}
/* (non-Javadoc)
* @see org.mt4j.input.inputAnalyzers.componentAnalyzers.AbstractComponentInputAnalyzer#getName()
*/
@Override
public String getName() {
return "Rotate Processor";
}
}