/src/org/mt4j/input/inputProcessors/componentProcessors/scaleProcessor/ScaleProcessor.java

http://mt4j.googlecode.com/ · Java · 407 lines · 229 code · 75 blank · 103 comment · 42 complexity · 97bb892222070505ee77a6f79377746e MD5 · raw file

  1. /***********************************************************************
  2. * mt4j Copyright (c) 2008 - 2009 C.Ruff, Fraunhofer-Gesellschaft All rights reserved.
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation, either version 3 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. *
  17. ***********************************************************************/
  18. package org.mt4j.input.inputProcessors.componentProcessors.scaleProcessor;
  19. import java.util.List;
  20. import org.mt4j.components.interfaces.IMTComponent3D;
  21. import org.mt4j.input.inputData.AbstractCursorInputEvt;
  22. import org.mt4j.input.inputData.InputCursor;
  23. import org.mt4j.input.inputData.MTFingerInputEvt;
  24. import org.mt4j.input.inputProcessors.IInputProcessor;
  25. import org.mt4j.input.inputProcessors.MTGestureEvent;
  26. import org.mt4j.input.inputProcessors.componentProcessors.AbstractComponentProcessor;
  27. import org.mt4j.input.inputProcessors.componentProcessors.AbstractCursorProcessor;
  28. import org.mt4j.util.math.Vector3D;
  29. import processing.core.PApplet;
  30. /**
  31. * The Class ScaleProcessor. 2-Finger Scale multi-touch gesture.
  32. * Fires ScaleEvent gesture events.
  33. * @author Christopher Ruff
  34. */
  35. public class ScaleProcessor extends AbstractCursorProcessor {
  36. /** The applet. */
  37. private PApplet applet;
  38. /** The scale context. */
  39. private ScaleContext sc;
  40. public ScaleProcessor(PApplet graphicsContext){
  41. this(graphicsContext, false);
  42. }
  43. /**
  44. * Instantiates a new scale processor.
  45. *
  46. * @param graphicsContext the graphics context
  47. */
  48. public ScaleProcessor(PApplet graphicsContext, boolean stopPropagation){
  49. super(stopPropagation);
  50. this.applet = graphicsContext;
  51. this.setLockPriority(2);
  52. }
  53. @Override
  54. public void cursorStarted(InputCursor newCursor, AbstractCursorInputEvt fEvt) {
  55. IMTComponent3D comp = fEvt.getTarget();
  56. logger.debug(this.getName() + " INPUT_STARTED, Cursor: " + newCursor.getId());
  57. List<InputCursor> alreadyLockedCursors = getLockedCursors();
  58. if (alreadyLockedCursors.size() >= 2){ //this gesture with 2 fingers already in progress
  59. //TODO get the 2 cursors which are most far away from each other
  60. InputCursor firstCursor = alreadyLockedCursors.get(0);
  61. InputCursor secondCursor = alreadyLockedCursors.get(1);
  62. if (isCursorDistanceGreater(firstCursor, secondCursor, newCursor) && canLock(firstCursor, newCursor)){
  63. ScaleContext newContext = new ScaleContext(firstCursor, newCursor, comp);
  64. if (!newContext.isGestureAborted()){ //Check if we could start gesture (ie. if fingers on component)
  65. sc = newContext;
  66. //Lock new Second cursor
  67. this.getLock(firstCursor, newCursor);
  68. this.unLock(secondCursor);
  69. logger.debug(this.getName() + " we could lock cursors: " + firstCursor.getId() +", and more far away cursor " + newCursor.getId());
  70. }else{
  71. logger.debug(this.getName() + " we could NOT exchange new second cursor - cursor not on component: " + firstCursor.getId() +", " + secondCursor.getId());
  72. }
  73. }else{
  74. 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());
  75. }
  76. }else{ //no gesture in progress yet
  77. List<InputCursor> availableCursors = getFreeComponentCursors();
  78. logger.debug(this.getName() + " Available cursors: " + availableCursors.size());
  79. if (availableCursors.size() >= 2){
  80. InputCursor otherCursor = getFarthestFreeComponentCursorTo(newCursor);
  81. // 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());
  82. if (this.canLock(otherCursor, newCursor)){ //TODO remove check, since alreday checked in getAvailableComponentCursors()?
  83. sc = new ScaleContext(otherCursor, newCursor, comp);
  84. if (!sc.isGestureAborted()){
  85. this.getLock(otherCursor, newCursor);
  86. logger.debug(this.getName() + " we could lock both cursors!");
  87. this.fireGestureEvent(new ScaleEvent(this, MTGestureEvent.GESTURE_STARTED, fEvt.getCurrentTarget(), otherCursor, newCursor, 1, 1, 1, sc.getSecondFingerNewPos()));
  88. }else{
  89. logger.debug(this.getName() + " gesture aborted, probably at least 1 finger not on component!");
  90. sc = null;
  91. }
  92. }else{
  93. logger.debug(this.getName() + " we could NOT lock both cursors!");
  94. }
  95. }else{
  96. logger.debug(this.getName() + " still missing a cursor to start gesture");
  97. }
  98. }
  99. }
  100. @Override
  101. public void cursorUpdated(InputCursor m, AbstractCursorInputEvt fEvt) {
  102. List<InputCursor> alreadyLockedCursors = getLockedCursors();
  103. if (sc != null && alreadyLockedCursors.size() == 2 && alreadyLockedCursors.contains(m)){
  104. float newFactor = sc.getUpdatedScaleFactor(m);
  105. if (m.equals(sc.getFirstFingerCursor())){
  106. this.fireGestureEvent(new ScaleEvent(this, MTGestureEvent.GESTURE_UPDATED, fEvt.getCurrentTarget(), sc.getFirstFingerCursor(), sc.getSecondFingerCursor(), newFactor, newFactor, 1, sc.getSecondFingerNewPos()));
  107. }else{
  108. this.fireGestureEvent(new ScaleEvent(this, MTGestureEvent.GESTURE_UPDATED, fEvt.getCurrentTarget(), sc.getFirstFingerCursor(), sc.getSecondFingerCursor(), newFactor, newFactor, 1, sc.getFirstFingerNewPos()));
  109. }
  110. }
  111. }
  112. @Override
  113. public void cursorEnded(InputCursor c, AbstractCursorInputEvt fEvt) {
  114. IMTComponent3D comp = fEvt.getTarget();
  115. logger.debug(this.getName() + " INPUT_ENDED -> Active cursors: " + getCurrentComponentCursors().size() + " Available cursors: " + getFreeComponentCursors().size() + " Locked cursors: " + getLockedCursors().size());
  116. if (getLockedCursors().contains(c)){
  117. InputCursor firstCursor = sc.getFirstFingerCursor();
  118. InputCursor secondCursor = sc.getSecondFingerCursor();
  119. if (firstCursor.equals(c) || secondCursor.equals(c)){ //The leaving cursor was used by the processor
  120. InputCursor leftOverCursor = firstCursor.equals(c) ? secondCursor : firstCursor;
  121. //TODO check if cursor is on component, else take next farthest cursor
  122. InputCursor futureCursor = getFarthestFreeCursorTo(leftOverCursor);
  123. if (futureCursor != null){ //already checked in getFartherstAvailableCursor() if we can lock it
  124. ScaleContext newContext = new ScaleContext(futureCursor, leftOverCursor, comp);
  125. if (!newContext.isGestureAborted()){
  126. sc = newContext;
  127. this.getLock(leftOverCursor, futureCursor);
  128. logger.debug(this.getName() + " continue with different cursors (ID: " + futureCursor.getId() + ")" + " " + "(ID: " + leftOverCursor.getId() + ")");
  129. }else{ //couldnt start gesture - cursor's not on component
  130. this.endGesture(leftOverCursor, fEvt, firstCursor, secondCursor);
  131. }
  132. }else{ //we cant use another cursor - End gesture
  133. this.endGesture(leftOverCursor, fEvt, firstCursor, secondCursor);
  134. }
  135. this.unLock(c);
  136. }
  137. }
  138. }
  139. private void endGesture(InputCursor leftOverCursor, AbstractCursorInputEvt fEvt, InputCursor firstCursor, InputCursor secondCursor){
  140. this.unLock(leftOverCursor);
  141. this.fireGestureEvent(new ScaleEvent(this, MTGestureEvent.GESTURE_ENDED, fEvt.getCurrentTarget(), firstCursor, secondCursor, 1, 1, 1, sc.getFirstFingerNewPos()));
  142. this.sc = null;
  143. }
  144. @Override
  145. public void cursorLocked(InputCursor c, IInputProcessor lockingAnalyzer) {
  146. if (lockingAnalyzer instanceof AbstractComponentProcessor){
  147. logger.debug(this.getName() + " Recieved MOTION LOCKED by (" + ((AbstractComponentProcessor)lockingAnalyzer).getName() + ") - cursor ID: " + c.getId());
  148. }else{
  149. logger.debug(this.getName() + " Recieved MOTION LOCKED by higher priority signal - cursor ID: " + c.getId());
  150. }
  151. if (sc != null && (sc.getFirstFingerCursor().equals(c) || sc.getSecondFingerCursor().equals(c))){ //Check if gesture was in progress
  152. this.unLockAllCursors();
  153. //FIXME TEST
  154. this.fireGestureEvent(new ScaleEvent(this, MTGestureEvent.GESTURE_CANCELED, c.getCurrentTarget(), sc.getFirstFingerCursor(), sc.getSecondFingerCursor(), 1, 1, 1, sc.getFirstFingerNewPos()));
  155. sc = null;
  156. logger.debug(this.getName() + " cursor:" + c.getId() + " CURSOR LOCKED. Was an active cursor in this gesture!");
  157. }
  158. }
  159. @Override
  160. public void cursorUnlocked(InputCursor c) {
  161. logger.debug(this.getName() + " Recieved UNLOCKED signal for cursor ID: " + c.getId());
  162. if (getLockedCursors().size() >= 2){ //we dont need the unlocked cursor, gesture still in progress
  163. return;
  164. }
  165. //Dont we have to unlock the cursors if there could still 1 be locked?
  166. //-> actually we always lock 2 cursors at once so should never be only 1 locked, but just for safety..
  167. this.unLockAllCursors();
  168. List<InputCursor> availableCursors = getFreeComponentCursors();
  169. if (availableCursors.size() >= 2){ //we can try to resume the gesture
  170. InputCursor firstCursor = availableCursors.get(0);
  171. InputCursor secondCursor = getFarthestFreeComponentCursorTo(firstCursor);
  172. //See if we can obtain a lock on both cursors
  173. IMTComponent3D comp = firstCursor.getFirstEvent().getTarget();
  174. ScaleContext newContext = new ScaleContext(firstCursor, secondCursor, comp);
  175. if (!newContext.isGestureAborted()){ //Check if we could start gesture (ie. if fingers on component)
  176. sc = newContext;
  177. this.getLock(firstCursor, secondCursor);
  178. //FIXME TEST
  179. this.fireGestureEvent(new ScaleEvent(this, MTGestureEvent.GESTURE_RESUMED, c.getCurrentTarget(), firstCursor, secondCursor, 1, 1, 1, sc.getSecondFingerNewPos()));
  180. logger.debug(this.getName() + " we could lock cursors: " + firstCursor.getId() +", " + secondCursor.getId());
  181. }else{
  182. sc = null;
  183. logger.debug(this.getName() + " we could NOT resume gesture - cursors not on component: " + firstCursor.getId() +", " + secondCursor.getId());
  184. }
  185. }
  186. }
  187. /**
  188. * The Class ScaleContext.
  189. */
  190. private class ScaleContext {
  191. /** The first finger cursor. */
  192. private InputCursor firstFingerCursor;
  193. /** The second finger cursor. */
  194. private InputCursor secondFingerCursor;
  195. /** The object. */
  196. private IMTComponent3D object;
  197. /** The first finger new pos. */
  198. private Vector3D firstFingerNewPos;
  199. /** The second finger new pos. */
  200. private Vector3D secondFingerNewPos;
  201. /** The second finger start pos. */
  202. private Vector3D secondFingerStartPos;
  203. /** The last scale distance. */
  204. private float lastScaleDistance;
  205. /** The scale plane normal. */
  206. private Vector3D scalePlaneNormal;
  207. /** The first finger start pos. */
  208. private Vector3D firstFingerStartPos;
  209. private boolean gestureAborted;
  210. /**
  211. * Instantiates a new scale context.
  212. *
  213. * @param firstFingerCursor the first finger cursor
  214. * @param secondFingerCursor the second finger cursor
  215. * @param object the object
  216. */
  217. public ScaleContext(InputCursor firstFingerCursor, InputCursor secondFingerCursor, IMTComponent3D object) {
  218. super();
  219. this.firstFingerCursor = firstFingerCursor;
  220. this.secondFingerCursor = secondFingerCursor;
  221. this.object = object;
  222. //irgendwo vorher checken ob der 1. finger ?berhaupt noch ?ber dem obj ist? ist nur sicher der fall wenn mit 1 finger gedraggt wird..
  223. Vector3D interPoint = getIntersection(applet, object, firstFingerCursor);
  224. if (interPoint !=null)
  225. firstFingerNewPos = interPoint;
  226. else{
  227. logger.warn(getName() + " firstFingerNewPos NEW = NULL");
  228. this.firstFingerNewPos = new Vector3D();
  229. gestureAborted = true;
  230. }
  231. Vector3D scndInterPoint = getIntersection(applet, object, secondFingerCursor);
  232. if (scndInterPoint !=null)
  233. secondFingerNewPos = scndInterPoint;
  234. else{
  235. logger.warn(getName() + " secondFingerNewPos NEW = NULL");
  236. secondFingerNewPos = new Vector3D();
  237. gestureAborted = true;
  238. }
  239. firstFingerStartPos = firstFingerNewPos.getCopy();
  240. secondFingerStartPos = secondFingerNewPos.getCopy();
  241. this.lastScaleDistance = Vector3D.distance(firstFingerNewPos, secondFingerNewPos);
  242. //Prevent scaling to 0 if both fingers are on the same position at scalstart
  243. if (lastScaleDistance == 0.0)
  244. lastScaleDistance = 1.0f;
  245. //TODO settable? get from objects? camera orthogonal?
  246. scalePlaneNormal = new Vector3D(0,0,1);
  247. /*
  248. newFingerMiddlePos = getMiddlePointBetweenFingers();
  249. oldFingerMiddlePos = newFingerMiddlePos.getCopy();
  250. */
  251. }
  252. public boolean isGestureAborted() {
  253. return gestureAborted;
  254. }
  255. /**
  256. * Gets the second finger cursor.
  257. *
  258. * @return the second finger cursor
  259. */
  260. public InputCursor getSecondFingerCursor() {
  261. return this.secondFingerCursor;
  262. }
  263. /**
  264. * Gets the first finger cursor.
  265. *
  266. * @return the first finger cursor
  267. */
  268. public InputCursor getFirstFingerCursor() {
  269. return this.firstFingerCursor;
  270. }
  271. /**
  272. * Gets the updated scale factor.
  273. *
  274. * @param m the m
  275. *
  276. * @return the updated scale factor
  277. */
  278. public float getUpdatedScaleFactor(InputCursor m){
  279. if (object == null || object.getViewingCamera() == null){ //IF component was destroyed while gesture still active
  280. this.gestureAborted = true;
  281. return 1;
  282. }
  283. //FIXME REMOVE!!
  284. // scalePlaneNormal = ((MTPolygon)object).getNormal();
  285. // logger.debug("scalePlaneNormal: " + scalePlaneNormal);
  286. // /*
  287. if (m.equals(firstFingerCursor)){ ///FIRST FINGER MOVED!
  288. Vector3D newFirstFingerPos = getPlaneIntersection(applet, scalePlaneNormal, firstFingerStartPos.getCopy(), firstFingerCursor);
  289. //Update the field
  290. if (newFirstFingerPos != null)
  291. this.firstFingerNewPos = newFirstFingerPos;
  292. }else if (m.equals(secondFingerCursor)){ ///SECOND FINGER MOVED!
  293. Vector3D newSecondFingerPos = getPlaneIntersection(applet, scalePlaneNormal, secondFingerStartPos.getCopy(), secondFingerCursor);
  294. // //TODO dragplane aus den beiden fingern ableiten -> wenn obj schr?g im raum, dragplane entsprechend
  295. // Vector3D newSecondFingerPos = ToolsIntersection.getRayPlaneIntersection(new Ray(rayStartPoint, newPointInRayDir), scalePlaneNormal, secondFingerStartPos.getCopy());
  296. //Update the field
  297. if (newSecondFingerPos != null)
  298. this.secondFingerNewPos = newSecondFingerPos;
  299. }
  300. //IF THE FINGERS ARE ON THE SAME POSITION RETURN 1 SO THAT NOT SCALING IS DONE
  301. //ELSE THE OBJECTS WILL DISAPPEAR, scaled to 0
  302. if (firstFingerNewPos.equalsVector(secondFingerNewPos))
  303. return 1.0f;
  304. float newScaleDistance = Vector3D.distance(firstFingerNewPos, secondFingerNewPos);
  305. float newScaleFactor = newScaleDistance/lastScaleDistance;
  306. lastScaleDistance = newScaleDistance;
  307. return newScaleFactor;
  308. // */
  309. // return 1;
  310. }
  311. /**
  312. * Gets the first finger new pos.
  313. *
  314. * @return the first finger new pos
  315. */
  316. public Vector3D getFirstFingerNewPos() {
  317. return firstFingerNewPos;
  318. }
  319. /**
  320. * Gets the second finger new pos.
  321. *
  322. * @return the second finger new pos
  323. */
  324. public Vector3D getSecondFingerNewPos() {
  325. return secondFingerNewPos;
  326. }
  327. }
  328. @Override
  329. public String getName() {
  330. return "Scale Processor";
  331. }
  332. }