/*********************************************************************** * 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.scaleProcessor; 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 ScaleProcessor. 2-Finger Scale multi-touch gesture. * Fires ScaleEvent gesture events. * @author Christopher Ruff */ public class ScaleProcessor extends AbstractCursorProcessor { /** The applet. */ private PApplet applet; // /** The unused cursors. */ // private List<InputCursor> unUsedCursors; // // /** The locked cursors. */ // private List<InputCursor> lockedCursors; /** The scale context. */ private ScaleContext sc; /** * Instantiates a new scale processor. * * @param graphicsContext the graphics context */ public ScaleProcessor(PApplet graphicsContext){ this.applet = graphicsContext; // this.unUsedCursors = new ArrayList<InputCursor>(); // this.lockedCursors = new ArrayList<InputCursor>(); this.setLockPriority(2); } @Override public void cursorStarted(InputCursor newCursor, MTFingerInputEvt fEvt) { // IMTComponent3D comp = fEvt.getTargetComponent(); // if (lockedCursors.size() >= 2){ //scale 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 scale 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)){ // ScaleContext newContext = new ScaleContext(otherCursor, m, comp); // if (!newContext.isGestureAborted()){ // sc = newContext; // //we can - now lock them // this.getLock(otherCursor, m); // unUsedCursors.remove(otherCursor); // lockedCursors.add(otherCursor); // lockedCursors.add(m); // logger.debug(this.getName() + " we could lock both cursors!"); // this.fireGestureEvent(new ScaleEvent(this, MTGestureEvent.GESTURE_DETECTED, comp, otherCursor, m, 1, 1, 1, sc.getSecondFingerNewPos())); // }else{ // sc = null; // unUsedCursors.add(m); // } // }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); // } // } IMTComponent3D comp = fEvt.getTargetComponent(); logger.debug(this.getName() + " INPUT_STARTED, Cursor: " + newCursor.getId()); List<InputCursor> alreadyLockedCursors = getLockedCursors(); if (alreadyLockedCursors.size() >= 2){ //this 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)){ ScaleContext newContext = new ScaleContext(firstCursor, newCursor, comp); if (!newContext.isGestureAborted()){ //Check if we could start gesture (ie. if fingers on component) sc = 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()? sc = new ScaleContext(otherCursor, newCursor, comp); if (!sc.isGestureAborted()){ this.getLock(otherCursor, newCursor); logger.debug(this.getName() + " we could lock both cursors!"); this.fireGestureEvent(new ScaleEvent(this, MTGestureEvent.GESTURE_DETECTED, comp, otherCursor, newCursor, 1, 1, 1, sc.getSecondFingerNewPos())); }else{ logger.debug(this.getName() + " gesture aborted, probably at least 1 finger not on component!"); sc = 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 fEvt) { IMTComponent3D comp = fEvt.getTargetComponent(); // if (lockedCursors.size() == 2 && lockedCursors.contains(m)){ // float newFactor = sc.getUpdatedScaleFactor(m); // //Use the other cursor as the scaling point // if (m.equals(sc.getFirstFingerCursor())){ // this.fireGestureEvent(new ScaleEvent(this, MTGestureEvent.GESTURE_UPDATED, comp, sc.getFirstFingerCursor(), sc.getSecondFingerCursor(), newFactor, newFactor, 1, sc.getSecondFingerNewPos())); // }else{ // this.fireGestureEvent(new ScaleEvent(this, MTGestureEvent.GESTURE_UPDATED, comp, sc.getFirstFingerCursor(), sc.getSecondFingerCursor(), newFactor, newFactor, 1, sc.getFirstFingerNewPos())); // } // } List<InputCursor> alreadyLockedCursors = getLockedCursors(); if (sc != null && alreadyLockedCursors.size() == 2 && alreadyLockedCursors.contains(m)){ float newFactor = sc.getUpdatedScaleFactor(m); if (m.equals(sc.getFirstFingerCursor())){ this.fireGestureEvent(new ScaleEvent(this, MTGestureEvent.GESTURE_UPDATED, comp, sc.getFirstFingerCursor(), sc.getSecondFingerCursor(), newFactor, newFactor, 1, sc.getSecondFingerNewPos())); }else{ this.fireGestureEvent(new ScaleEvent(this, MTGestureEvent.GESTURE_UPDATED, comp, sc.getFirstFingerCursor(), sc.getSecondFingerCursor(), newFactor, newFactor, 1, sc.getFirstFingerNewPos())); } } } @Override public void cursorEnded(InputCursor c, MTFingerInputEvt fEvt) { IMTComponent3D comp = fEvt.getTargetComponent(); logger.debug(this.getName() + " INPUT_ENDED -> Active cursors: " + getCurrentComponentCursors().size() + " Available cursors: " + getFreeComponentCursors().size() + " Locked cursors: " + getLockedCursors().size()); if (getLockedCursors().contains(c)){ InputCursor firstCursor = sc.getFirstFingerCursor(); InputCursor secondCursor = sc.getSecondFingerCursor(); if (firstCursor.equals(c) || secondCursor.equals(c)){ //The leaving cursor was used by the processor InputCursor leftOverCursor = firstCursor.equals(c) ? secondCursor : firstCursor; //TODO check if cursor is on component, else take next farthest cursor InputCursor futureCursor = getFarthestFreeCursorTo(leftOverCursor, getCurrentComponentCursorsArray()); if (futureCursor != null){ //already checked in getFartherstAvailableCursor() if we can lock it ScaleContext newContext = new ScaleContext(futureCursor, leftOverCursor, comp); if (!newContext.isGestureAborted()){ sc = 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 ScaleEvent(this, MTGestureEvent.GESTURE_ENDED, component, firstCursor, secondCursor, 1, 1, 1, sc.getFirstFingerNewPos())); this.sc = null; } /* (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 MOTION LOCKED by (" + ((AbstractComponentProcessor)lockingAnalyzer).getName() + ") - cursor ID: " + c.getId()); }else{ logger.debug(this.getName() + " Recieved MOTION LOCKED by higher priority signal - cursor ID: " + c.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!"); // } if (sc != null && (sc.getFirstFingerCursor().equals(c) || sc.getSecondFingerCursor().equals(c))){ //TODO do we have to unlock the 2nd cursor, besides "c" ?? this.unLockAllCursors(); sc = 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 c) { logger.debug(this.getName() + " Recieved UNLOCKED signal for cursor ID: " + c.getId()); // if (lockedCursors.size() >= 2){ //we dont need the unlocked cursor, gesture still in progress // return; // } // // if (unUsedCursors.contains(c)){ //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); // // if (this.canLock(firstCursor, secondCursor)){ // IMTComponent3D comp = firstCursor.getFirstEvent().getTargetComponent(); // ScaleContext newContext = new ScaleContext(firstCursor, secondCursor, comp); // if (!newContext.isGestureAborted()){ // sc = newContext; // 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()); // //TODO fire started evt? //// this.fireGestureEvent(new ScaleEvent(this, MTGestureEvent.GESTURE_DETECTED, comp, firstCursor, secondCursor, 1, 1, 1, sc.getSecondFingerNewPos())); // }else{ // sc = null; // logger.debug(this.getName() + " we could NOT resume gesture - cursors not on component: " + firstCursor.getId() +", " + secondCursor.getId()); // } // }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?"); // } 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(); ScaleContext newContext = new ScaleContext(firstCursor, secondCursor, comp); if (!newContext.isGestureAborted()){ //Check if we could start gesture (ie. if fingers on component) sc = newContext; this.getLock(firstCursor, secondCursor); logger.debug(this.getName() + " we could lock cursors: " + firstCursor.getId() +", " + secondCursor.getId()); }else{ sc = null; logger.debug(this.getName() + " we could NOT resume gesture - cursors not on component: " + firstCursor.getId() +", " + secondCursor.getId()); } } } /** * The Class ScaleContext. */ private class ScaleContext { /** The first finger cursor. */ private InputCursor firstFingerCursor; /** The second finger cursor. */ private InputCursor secondFingerCursor; /** The object. */ private IMTComponent3D object; /** The first finger new pos. */ private Vector3D firstFingerNewPos; /** The second finger new pos. */ private Vector3D secondFingerNewPos; /** The second finger start pos. */ private Vector3D secondFingerStartPos; /** The last scale distance. */ private float lastScaleDistance; /** The scale plane normal. */ private Vector3D scalePlaneNormal; /** The new finger middle pos. */ private Vector3D newFingerMiddlePos; /** The old finger middle pos. */ private Vector3D oldFingerMiddlePos; /** The first finger start pos. */ private Vector3D firstFingerStartPos; private boolean gestureAborted; /** * Instantiates a new scale context. * * @param firstFingerCursor the first finger cursor * @param secondFingerCursor the second finger cursor * @param object the object */ public ScaleContext(InputCursor firstFingerCursor, InputCursor secondFingerCursor, IMTComponent3D object) { super(); this.firstFingerCursor = firstFingerCursor; this.secondFingerCursor = secondFingerCursor; this.object = object; //irgendwo vorher checken ob der 1. finger �berhaupt noch �ber dem obj ist? ist nur sicher der fall wenn mit 1 finger gedraggt wird.. Vector3D interPoint = object.getIntersectionGlobal( Tools3D.getCameraPickRay(applet, object, firstFingerCursor.getCurrentEvent().getPosX(), firstFingerCursor.getCurrentEvent().getPosY())); if (interPoint !=null) firstFingerNewPos = interPoint; else{ logger.warn(getName() + " firstFingerNewPos NEW = NULL"); this.firstFingerNewPos = new Vector3D(); gestureAborted = true; } Vector3D scndInterPoint = object.getIntersectionGlobal( Tools3D.getCameraPickRay(applet, object, secondFingerCursor.getCurrentEvent().getPosX(), secondFingerCursor.getCurrentEvent().getPosY())); if (scndInterPoint !=null) secondFingerNewPos = scndInterPoint; else{ logger.warn(getName() + " secondFingerNewPos NEW = NULL"); secondFingerNewPos = new Vector3D(); gestureAborted = true; } firstFingerStartPos = firstFingerNewPos.getCopy(); secondFingerStartPos = secondFingerNewPos.getCopy(); this.lastScaleDistance = Vector3D.distance(firstFingerNewPos, secondFingerNewPos); //Prevent scaling to 0 if both fingers are on the same position at scalstart if (lastScaleDistance == 0.0) lastScaleDistance = 1.0f; //TODO settable? get from objects? camera orthogonal? scalePlaneNormal = new Vector3D(0,0,1); /* newFingerMiddlePos = getMiddlePointBetweenFingers(); oldFingerMiddlePos = newFingerMiddlePos.getCopy(); */ } public boolean isGestureAborted() { return gestureAborted; } /** * Gets the second finger cursor. * * @return the second finger cursor */ public InputCursor getSecondFingerCursor() { return this.secondFingerCursor; } /** * Gets the first finger cursor. * * @return the first finger cursor */ public InputCursor getFirstFingerCursor() { return this.firstFingerCursor; } /** * Gets the object. * * @return the object */ public IMTComponent3D getObject() { return this.object; } /** * Gets the updated scale values. * * @param m the m * * @return the updated scale values */ public float[] getUpdatedScaleValues(InputCursor m){ //TODO make it possible to scale x only, or y only? -> resize return new float[3]; } /** * Gets the updated scale factor. * * @param m the m * * @return the updated scale factor */ public float getUpdatedScaleFactor(InputCursor m){ if (object == null || object.getViewingCamera() == null){ //IF component was destroyed while gesture still active this.gestureAborted = true; return 1; } //FIXME REMOVE!! // scalePlaneNormal = ((MTPolygon)object).getNormal(); // logger.debug("scalePlaneNormal: " + scalePlaneNormal); // /* if (m.equals(firstFingerCursor)){ ///FIRST FINGER MOVED! Vector3D newFirstFingerPos = ToolsGeometry.getRayPlaneIntersection( Tools3D.getCameraPickRay(applet, object, firstFingerCursor.getCurrentEvent().getPosX(), firstFingerCursor.getCurrentEvent().getPosY()), scalePlaneNormal, firstFingerStartPos.getCopy()); //Update the field if (newFirstFingerPos != null) this.firstFingerNewPos = newFirstFingerPos; }else if (m.equals(secondFingerCursor)){ ///SECOND FINGER MOVED! Vector3D newSecondFingerPos = ToolsGeometry.getRayPlaneIntersection( Tools3D.getCameraPickRay(applet, object, secondFingerCursor.getCurrentEvent().getPosX(), secondFingerCursor.getCurrentEvent().getPosY()), scalePlaneNormal, secondFingerStartPos.getCopy()); // //TODO dragplane aus den beiden fingern ableiten -> wenn obj schr�g im raum, dragplane entsprechend // Vector3D newSecondFingerPos = ToolsIntersection.getRayPlaneIntersection(new Ray(rayStartPoint, newPointInRayDir), scalePlaneNormal, secondFingerStartPos.getCopy()); //Update the field if (newSecondFingerPos != null) this.secondFingerNewPos = newSecondFingerPos; } //IF THE FINGERS ARE ON THE SAME POSITION RETURN 1 SO THAT NOT SCALING IS DONE //ELSE THE OBJECTS WILL DISAPPEAR, scaled to 0 if (firstFingerNewPos.equalsVector(secondFingerNewPos)) return 1.0f; float newScaleDistance = Vector3D.distance(firstFingerNewPos, secondFingerNewPos); float newScaleFactor = newScaleDistance/lastScaleDistance; lastScaleDistance = newScaleDistance; return newScaleFactor; // */ // return 1; } /** * Gets the updated middle finger pos delta. * * @return the updated middle finger pos delta */ public Vector3D getUpdatedMiddleFingerPosDelta(){ //TODO REMOVE? 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 = secondFingerNewPos.getSubtracted(firstFingerNewPos); //= Richtungsvektor vom 1. zum 2. finger bla.scaleLocal(0.5f); //take the half return (new Vector3D(firstFingerNewPos.getX() + bla.getX(), firstFingerNewPos.getY() + bla.getY(), firstFingerNewPos.getZ() + bla.getZ())); } /** * Gets the first finger new pos. * * @return the first finger new pos */ public Vector3D getFirstFingerNewPos() { return firstFingerNewPos; } /** * Gets the second finger new pos. * * @return the second finger new pos */ public Vector3D getSecondFingerNewPos() { return secondFingerNewPos; } } @Override public String getName() { return "Scale Processor"; } }