/src/org/mt4j/input/inputProcessors/componentProcessors/rotateProcessor/RotateProcessor.java
Java | 468 lines | 266 code | 82 blank | 120 comment | 45 complexity | 41945272f45691541544d66099e0fe47 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 ***********************************************************************/ 18package org.mt4j.input.inputProcessors.componentProcessors.rotateProcessor; 19 20import java.util.List; 21 22import org.mt4j.components.interfaces.IMTComponent3D; 23import org.mt4j.input.inputData.AbstractCursorInputEvt; 24import org.mt4j.input.inputData.InputCursor; 25import org.mt4j.input.inputData.MTFingerInputEvt; 26import org.mt4j.input.inputProcessors.IInputProcessor; 27import org.mt4j.input.inputProcessors.MTGestureEvent; 28import org.mt4j.input.inputProcessors.componentProcessors.AbstractComponentProcessor; 29import org.mt4j.input.inputProcessors.componentProcessors.AbstractCursorProcessor; 30import org.mt4j.util.math.Vector3D; 31 32import processing.core.PApplet; 33 34/** 35 * The Class RotateProcessor. Rotation multitouch gesture. 36 * Fires RotateEvent gesture events. 37 * @author Christopher Ruff 38 */ 39public class RotateProcessor extends AbstractCursorProcessor { 40 41 /** The applet. */ 42 private PApplet applet; 43 44 /** The rc. */ 45 private RotationContext rc; 46 47 /** The drag plane normal. */ 48 private Vector3D dragPlaneNormal; 49 50 51 public RotateProcessor(PApplet graphicsContext){ 52 this(graphicsContext, false); 53 } 54 55 /** 56 * Instantiates a new rotate processor. 57 * 58 * @param graphicsContext the graphics context 59 */ 60 public RotateProcessor(PApplet graphicsContext, boolean stopEventPropagation){ 61 super(stopEventPropagation); 62 this.applet = graphicsContext; 63 this.dragPlaneNormal = new Vector3D(0,0,1); 64 this.setLockPriority(2); 65 } 66 67 68 69 @Override 70 public void cursorStarted(InputCursor newCursor, AbstractCursorInputEvt positionEvent) { 71 IMTComponent3D comp = positionEvent.getTarget(); 72 logger.debug(this.getName() + " INPUT_STARTED, Cursor: " + newCursor.getId()); 73 74 List<InputCursor> alreadyLockedCursors = getLockedCursors(); 75 if (alreadyLockedCursors.size() >= 2){ //gesture with 2 fingers already in progress 76 //TODO get the 2 cursors which are most far away from each other 77 InputCursor firstCursor = alreadyLockedCursors.get(0); 78 InputCursor secondCursor = alreadyLockedCursors.get(1); 79 //Check if the new finger is more far away, so use that one 80 if (isCursorDistanceGreater(firstCursor, secondCursor, newCursor) && canLock(firstCursor, newCursor)){ 81 RotationContext newContext = new RotationContext(firstCursor, newCursor, comp); 82 if (!newContext.isGestureAborted()){ //Check if we could start gesture (ie. if fingers on component) 83 rc = newContext; 84 //Lock new Second cursor 85 this.getLock(firstCursor, newCursor); 86 this.unLock(secondCursor); 87 logger.debug(this.getName() + " we could lock cursors: " + firstCursor.getId() +", and more far away cursor " + newCursor.getId()); 88 }else{ 89 logger.debug(this.getName() + " we could NOT exchange new second cursor - cursor not on component: " + firstCursor.getId() +", " + secondCursor.getId()); 90 } 91 }else{ 92 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()); 93 } 94 }else{ //no gesture in progress yet 95 List<InputCursor> availableCursors = getFreeComponentCursors(); 96 logger.debug(this.getName() + " Available cursors: " + availableCursors.size()); 97 if (availableCursors.size() >= 2){ 98 InputCursor otherCursor = getFarthestFreeComponentCursorTo(newCursor); 99// 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()); 100 101 if (this.canLock(otherCursor, newCursor)){ //TODO remove check, since alreday checked in getAvailableComponentCursors()? 102 rc = new RotationContext(otherCursor, newCursor, comp); 103 if (!rc.isGestureAborted()){ 104 this.getLock(otherCursor, newCursor); 105 logger.debug(this.getName() + " we could lock both cursors!"); 106 this.fireGestureEvent(new RotateEvent(this, MTGestureEvent.GESTURE_STARTED, positionEvent.getCurrentTarget(), otherCursor, newCursor, Vector3D.ZERO_VECTOR, rc.getRotationPoint(), 0f)); 107 }else{ 108 logger.debug(this.getName() + " gesture aborted, probably at least 1 finger not on component!"); 109 rc = null; 110 } 111 }else{ 112 logger.debug(this.getName() + " we could NOT lock both cursors!"); 113 } 114 }else{ 115 logger.debug(this.getName() + " still missing a cursor to start gesture"); 116 } 117 } 118 } 119 120 121 @Override 122 public void cursorUpdated(InputCursor m, AbstractCursorInputEvt positionEvent) { 123 List<InputCursor> alreadyLockedCursors = getLockedCursors(); 124 if (rc != null && alreadyLockedCursors.size() == 2 && alreadyLockedCursors.contains(m)){ 125 float rotationAngleDegrees = rc.updateAndGetRotationAngle(m); 126 this.fireGestureEvent(new RotateEvent(this, MTGestureEvent.GESTURE_UPDATED, positionEvent.getCurrentTarget(), rc.getPinFingerCursor(), rc.getRotateFingerCursor(), Vector3D.ZERO_VECTOR, rc.getRotationPoint(), rotationAngleDegrees)); 127 } 128 } 129 130 131 @Override 132 public void cursorEnded(InputCursor c, AbstractCursorInputEvt positionEvent) { 133 IMTComponent3D comp = positionEvent.getTarget(); 134 logger.debug(this.getName() + " INPUT_ENDED RECIEVED - CURSOR: " + c.getId()); 135 logger.debug("Rotate ended -> Active cursors: " + getCurrentComponentCursors().size() + " Available cursors: " + getFreeComponentCursors().size() + " Locked cursors: " + getLockedCursors().size()); 136 if (getLockedCursors().contains(c)){ 137 InputCursor firstCursor = rc.getFirstCursor(); 138 InputCursor secondCursor = rc.getSecondCursor(); 139 if (firstCursor.equals(c) || secondCursor.equals(c)){ //The leaving cursor was used by the processor 140 InputCursor leftOverCursor = firstCursor.equals(c) ? secondCursor : firstCursor; 141 InputCursor futureCursor = getFarthestFreeCursorTo(leftOverCursor); 142 if (futureCursor != null){ //already checked in getFartherstAvailableCursor() if we can lock it 143 RotationContext newContext = new RotationContext(futureCursor, leftOverCursor, comp); 144 if (!newContext.isGestureAborted()){ 145 rc = newContext; 146 this.getLock(leftOverCursor, futureCursor); 147 logger.debug(this.getName() + " continue with different cursors (ID: " + futureCursor.getId() + ")" + " " + "(ID: " + leftOverCursor.getId() + ")"); 148 }else{ //couldnt start gesture - cursor's not on component 149 this.endGesture(leftOverCursor, positionEvent, firstCursor, secondCursor); 150 } 151 }else{ //we cant use another cursor - End gesture 152 this.endGesture(leftOverCursor, positionEvent, firstCursor, secondCursor); 153 } 154 this.unLock(c); 155 } 156 } 157 } 158 159 private void endGesture(InputCursor leftOverCursor, AbstractCursorInputEvt positionEvent, InputCursor firstCursor, InputCursor secondCursor){ 160 this.unLock(leftOverCursor); 161 this.fireGestureEvent(new RotateEvent(this, MTGestureEvent.GESTURE_ENDED, positionEvent.getCurrentTarget(), firstCursor, secondCursor, Vector3D.ZERO_VECTOR, rc.getRotationPoint(), 0)); 162 this.rc = null; 163 } 164 165 166 @Override 167 public void cursorLocked(InputCursor c, IInputProcessor lockingAnalyzer) { 168 if (lockingAnalyzer instanceof AbstractComponentProcessor){ 169 logger.debug(this.getName() + " Recieved CURSOR LOCKED by (" + ((AbstractComponentProcessor)lockingAnalyzer).getName() + ") - cursor ID: " + c.getId()); 170 }else{ 171 logger.debug(this.getName() + " Recieved CURSOR LOCKED by higher priority signal - cursor ID: " + c.getId()); 172 } 173 174 if (rc != null && (rc.getFirstCursor().equals(c) || rc.getSecondCursor().equals(c))){ 175 //TODO do we have to unlock the 2nd cursor, besides "c" ?? 176 this.unLockAllCursors(); 177 //FIXME TEST 178 this.fireGestureEvent(new RotateEvent(this, MTGestureEvent.GESTURE_CANCELED, c.getCurrentTarget(), rc.getFirstCursor(), rc.getSecondCursor(), Vector3D.ZERO_VECTOR, rc.getRotationPoint(), 0)); 179 rc = null; 180 logger.debug(this.getName() + " cursor:" + c.getId() + " CURSOR LOCKED. Was an active cursor in this gesture!"); 181 } 182 } 183 184 185 @Override 186 public void cursorUnlocked(InputCursor c) { 187 logger.debug(this.getName() + " Recieved UNLOCKED signal for cursor ID: " + c.getId()); 188 189 if (getLockedCursors().size() >= 2){ //we dont need the unlocked cursor, gesture still in progress 190 return; 191 } 192 193 //Dont we have to unlock the cursors if there could still 1 be locked? 194 //-> actually we always lock 2 cursors at once so should never be only 1 locked, but just for safety.. 195 this.unLockAllCursors(); 196 197 List<InputCursor> availableCursors = getFreeComponentCursors(); 198 if (availableCursors.size() >= 2){ //we can try to resume the gesture 199 InputCursor firstCursor = availableCursors.get(0); 200 InputCursor secondCursor = getFarthestFreeComponentCursorTo(firstCursor); 201 202 //See if we can obtain a lock on both cursors 203 IMTComponent3D comp = firstCursor.getFirstEvent().getTarget(); 204 RotationContext newContext = new RotationContext(firstCursor, secondCursor, comp); 205 if (!newContext.isGestureAborted()){ //Check if we could start gesture (i.e. if fingers on component) 206 rc = newContext; 207 this.getLock(firstCursor, secondCursor); 208 //FIXME TEST 209 this.fireGestureEvent(new RotateEvent(this, MTGestureEvent.GESTURE_RESUMED, c.getCurrentTarget(), firstCursor, secondCursor, Vector3D.ZERO_VECTOR, rc.getRotationPoint(), 0f)); 210 logger.debug(this.getName() + " we could lock cursors: " + firstCursor.getId() +", " + secondCursor.getId()); 211 }else{ 212 rc = null; 213 logger.debug(this.getName() + " we could NOT resume gesture - cursors not on component: " + firstCursor.getId() +", " + secondCursor.getId()); 214 } 215 } 216 } 217 218 219 220 221 222 /** 223 * The Class RotationContext. 224 */ 225 private class RotationContext { 226 227 /** The pin finger start. */ 228 private Vector3D pinFingerStart; 229 230 /** The pin finger last. */ 231 private Vector3D pinFingerLast; 232 233 /** The pin finger new. */ 234 private Vector3D pinFingerNew; 235 236 /** The rotate finger start. */ 237 private Vector3D rotateFingerStart; 238 239 /** The rotate finger last. */ 240 private Vector3D rotateFingerLast; 241 242 /** The rotate finger new. */ 243 private Vector3D rotateFingerNew; 244 245 /** The last rotation vect. */ 246 private Vector3D lastRotationVect; 247 248 /** The object. */ 249 private IMTComponent3D object; 250 251 /** The rotation point. */ 252 private Vector3D rotationPoint; 253 254 /** The pin finger cursor. */ 255 private InputCursor pinFingerCursor; 256 257 /** The rotate finger cursor. */ 258 private InputCursor rotateFingerCursor; 259 260 private boolean gestureAborted; 261 262 /** 263 * Instantiates a new rotation context. 264 * 265 * @param pinFingerCursor the pin finger cursor 266 * @param rotateFingerCursor the rotate finger cursor 267 * @param object the object 268 */ 269 public RotationContext(InputCursor pinFingerCursor, InputCursor rotateFingerCursor, IMTComponent3D object){ 270 this.pinFingerCursor = pinFingerCursor; 271 this.rotateFingerCursor = rotateFingerCursor; 272 273// Vector3D interPoint = getIntersection(applet, object, pinFingerCursor); 274 Vector3D interPoint = getIntersection(applet, pinFingerCursor.getCurrentEvent().getCurrentTarget(), pinFingerCursor); 275 if (interPoint !=null) 276 pinFingerNew = interPoint; 277 else{ 278 logger.warn(getName() + " Pinfinger NEW = NULL"); 279 pinFingerNew = new Vector3D(); 280 gestureAborted = true; 281 } 282 283 //Use lastEvent when resuming with another cursor that started long ago 284// Vector3D interPointRot = getIntersection(applet, object, rotateFingerCursor); 285 Vector3D interPointRot = getIntersection(applet, object, rotateFingerCursor); 286 287 if (interPointRot !=null) 288 rotateFingerStart = interPointRot; 289 else{ 290 logger.warn(getName() + " rotateFingerStart = NULL"); 291 rotateFingerStart = new Vector3D(); 292 //TODO ABORT THE Rotation HERE! 293 gestureAborted = true; 294 } 295 296 this.pinFingerStart = pinFingerNew.getCopy(); 297 298 this.pinFingerLast = pinFingerStart.getCopy(); 299 300 this.rotateFingerLast = rotateFingerStart.getCopy(); 301 this.rotateFingerNew = rotateFingerStart.getCopy(); 302 303 this.object = object; 304 305 this.rotationPoint = pinFingerNew.getCopy(); 306 307 //Get the rotation vector for reference for the next rotation 308 this.lastRotationVect = rotateFingerStart.getSubtracted(pinFingerNew); 309 310 //FIXME REMOVE! 311// dragPlaneNormal = ((MTPolygon)object).getNormal(); 312// logger.debug("DragNormal: " + dragPlaneNormal); 313 } 314 315 /** 316 * Update and get rotation angle. 317 * 318 * @param moveCursor the move cursor 319 * 320 * @return the float 321 */ 322 public float updateAndGetRotationAngle(InputCursor moveCursor) { 323// /* 324 float newAngleRad; 325 float newAngleDegrees; 326 327 //save the current pinfinger location as the old one 328 this.pinFingerLast = this.pinFingerNew; 329 //save the current pinfinger location as the old one 330 this.rotateFingerLast = this.rotateFingerNew; 331 332 //Check which finger moved and has to be updated 333 if (moveCursor.equals(pinFingerCursor)){ 334 updatePinFinger(); 335 } 336 else if (moveCursor.equals(rotateFingerCursor)){ 337 updateRotateFinger(); 338 } 339 340 //FIXME REMOVE! 341// dragPlaneNormal = ((MTPolygon)object).getNormal(); 342// logger.debug("DragNormal: " + dragPlaneNormal); 343 344 //TODO drop Z values after that? 345 //calculate the vector between the rotation finger vectors 346 Vector3D currentRotationVect = rotateFingerNew.getSubtracted(pinFingerNew).normalizeLocal(); //TEST normalize rotation vector 347 348 //calculate the angle between the rotaion finger vectors 349 newAngleRad = Vector3D.angleBetween(lastRotationVect, currentRotationVect); 350 newAngleDegrees = (float)Math.toDegrees(newAngleRad); 351 352 //FIXME EXPERIMENTAL BECAUSE ANGLEBETWEEN GIVES ROTATIONS SOMETIMES WHEN BOTH VECTORS ARE EQUAL!? 353 if (rotateFingerLast.equalsVector(rotateFingerNew) && pinFingerLast.equalsVector(pinFingerNew)){ 354 //logger.debug("Angle gleich lassen"); 355 newAngleDegrees = 0.0f; 356 }else{ 357 //logger.debug("Neuer Angle: " + newAngleDegrees); 358 } 359 360// logger.debug("lastRotVect: " + lastRotationVect + " currentROtationVect: " + currentRotationVect + " Deg: " + newAngleDegrees); 361 362 Vector3D cross = lastRotationVect.getCross(currentRotationVect); 363 //Get the direction of rotation 364 if (cross.getZ() < 0){ 365 newAngleDegrees*=-1; 366 } 367 368 //Check if the current and last rotation vectors are equal or not 369 if (!Float.isNaN(newAngleDegrees)/*!String.valueOf(newAngleDegrees).equalsIgnoreCase("NaN")*/){ 370 //if (newAngleDegrees != Float.NaN){ //if the vectors are equal rotationangle is NAN? 371 lastRotationVect = currentRotationVect; 372 return newAngleDegrees; 373 }else{ 374 //lastRotationVect = currentRotationVect; 375 return 0; 376 } 377// */ 378// return 0; 379 } 380 381 382 /** 383 * Update rotate finger. 384 */ 385 private void updateRotateFinger(){ 386 if (object == null || object.getViewingCamera() == null){ //IF component was destroyed while gesture still active 387 this.gestureAborted = true; 388 return ; 389 } 390 Vector3D newRotateFingerPos = getPlaneIntersection(applet, dragPlaneNormal, rotateFingerStart.getCopy(), rotateFingerCursor); 391 //Update the field 392 if (newRotateFingerPos != null){ 393 this.rotateFingerNew = newRotateFingerPos; 394 this.rotationPoint = pinFingerNew; 395 }else{ 396 logger.error(getName() + " new newRotateFinger Pos = null at update"); 397 } 398 } 399 400 401 /** 402 * Update pin finger. 403 */ 404 private void updatePinFinger(){ 405 if (object == null){ //IF component was destroyed while gesture still active 406 this.gestureAborted = true; 407 return; 408 } 409 Vector3D newPinFingerPos = getPlaneIntersection(applet, dragPlaneNormal, pinFingerStart.getCopy(), pinFingerCursor); 410 411 if (newPinFingerPos != null){ 412 // Update pinfinger with new position 413 this.pinFingerNew = newPinFingerPos; 414 //Set the Rotation finger as the rotation point because the pinfinger was moved 415 this.rotationPoint = rotateFingerNew; //REMOVE!!? made because of scale 416 }else{ 417 logger.error(getName() + " new PinFinger Pos = null at update"); 418 } 419 } 420 421 422 /** 423 * Gets the rotation point. 424 * 425 * @return the rotation point 426 */ 427 public Vector3D getRotationPoint() { 428 return rotationPoint; 429 } 430 431 /** 432 * Gets the pin finger cursor. 433 * 434 * @return the pin finger cursor 435 */ 436 public InputCursor getPinFingerCursor() { 437 return pinFingerCursor; 438 } 439 440 /** 441 * Gets the rotate finger cursor. 442 * 443 * @return the rotate finger cursor 444 */ 445 public InputCursor getRotateFingerCursor() { 446 return rotateFingerCursor; 447 } 448 449 public boolean isGestureAborted() { 450 return gestureAborted; 451 } 452 453 public InputCursor getFirstCursor(){ 454 return this.pinFingerCursor; 455 } 456 457 public InputCursor getSecondCursor(){ 458 return this.rotateFingerCursor; 459 } 460 461 } 462 463 @Override 464 public String getName() { 465 return "Rotate Processor"; 466 } 467 468}