package org.pollux3d.gesture;
import java.util.ArrayList;
import java.util.List;
import org.pollux3d.tuio.TuioInputListener;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
public class GestureSystem implements TuioInputListener {
private static float SingelTouchTimout = 1f;
private static int longClickMaxDelta = 1;
private float timer = 0;
private boolean twoCursors = false;
class CursorStore {
int x;
int y;
int xDelta;
int yDelta;
boolean isNew;
public CursorStore (int xDelta, int yDelta, int x, int y, boolean isNew) {
this.x = x;
this.y = y;
this.xDelta = xDelta;
this.yDelta = yDelta;
this.isNew = isNew;
}
public CursorStore (int xDelta, int yDelta, int x, int y) {
this(xDelta, yDelta, x, y, false);
}
public CursorStore (int x, int y) {
this(0, 0, x, y, true);
}
public Vector2f getVector() {
return new Vector2f(x, y);
}
public Vector2f getPreVector() {
return new Vector2f(x+xDelta, y-yDelta);
}
public boolean hasNotMoved() {
return (xDelta == 0 && yDelta == 0);
}
public boolean isNew() {
return isNew;
}
}
private CursorStore cursor1 = null;
private CursorStore cursor2 = null;
private CursorStore lastCursor1 = null;
private CursorStore lastCursor2 = null;
/**
* Singleton instance
*/
private static GestureSystem instance = new GestureSystem();
/**
* List of active listeners
*/
protected List<GestureListener> listeners;
private GestureSystem() {};
public static GestureSystem get() {
return instance;
}
private boolean isTimerExpired() {
return timer <= 0;
}
private void tickTimer(float tpf) {
if (timer < 0) return;
timer -= tpf;
}
private void resetTimer() {
timer = SingelTouchTimout;
}
public void update(float tpf) {
// check if we have one or two cursors
if (cursor1 == null || cursor2 == null) {
tickTimer(tpf);
// update single touch system
updateSingleTouch(twoCursors);
} else if (cursor1 != null && cursor2 != null) {
resetTimer();
// update multi touch system
updateMultiTouch();
// set the two cursor flag to ignore click events
twoCursors = true;
}
// no cursors then reset the twoCursors flag
if (cursor1 == null && cursor2 == null && twoCursors) {
twoCursors = false;
}
}
private void updateSingleTouch(boolean twoCursors) {
//TODO: fix click event when two fingers a released at the same time
// get the cursors for long click event
CursorStore cursor = null;
if (cursor1 != null) {
cursor = cursor1;
} else if (cursor2 != null){
cursor = cursor2;
}
// if we have an cursor and the timer expired
if (cursor != null && !twoCursors) {
// if time is expired reset timer and trigger event
if (isTimerExpired()) {
resetTimer();
Vector2f longClick = cursor.getVector();
for (int i = 0; i < listeners.size(); i++) {
GestureListener listener = listeners.get(i);
listener.longClick(longClick);
}
} else {
// if the cursor has move over the longClickMaxDelta reset the Timer
if (FastMath.abs(cursor.xDelta) > longClickMaxDelta ||
FastMath.abs(cursor.yDelta) > longClickMaxDelta ) {
resetTimer();
}
}
}
// get the last cursor for up and click event
CursorStore lastCursor = null;
if (lastCursor1 != null && lastCursor2 == null) {
lastCursor = lastCursor1;
lastCursor1 = null;
} else if (lastCursor2 != null && lastCursor1 == null){
lastCursor = lastCursor2;
lastCursor2 = null;
} else {
// no last cursor or multitouch event ended
return;
}
// we know we have an up event -> reset long click timer
resetTimer();
Vector2f up = lastCursor.getVector();
// loop through listeners and trigger up and click events
for (int i = 0; i < listeners.size(); i++) {
GestureListener listener = listeners.get(i);
listener.up(up);
if (cursor1 == null && cursor2 == null && !twoCursors) {
listener.click(up);
}
}
}
private void updateMultiTouch() {
if (cursor1.hasNotMoved() && cursor2.hasNotMoved()) return;
if (cursor1.isNew() || cursor2.isNew()) return;
float distancePre = FastMath.abs(cursor1.getPreVector().distance(cursor2.getPreVector()));
float distanceNow = FastMath.abs(cursor1.getVector().distance(cursor2.getVector()));
float zoom = distanceNow - distancePre;
Vector2f nPre = cursor1.getPreVector().subtract(cursor2.getPreVector()).normalize();
Vector2f nNow = cursor1.getVector().subtract(cursor2.getVector()).normalize();
// scalar product -> angle
float angle = FastMath.acos(nPre.x * nNow.x + nPre.y * nNow.y);
//TODO: Find a better calculation
if (!isPosRotation(nPre, nNow, angle)) angle = -angle;
Vector2f pre = cursor1.getVector().subtract(cursor1.getPreVector());
Vector2f now = cursor2.getVector().subtract(cursor2.getPreVector());
Vector2f move = pre.add(now).divide(2f);
for (int i = 0; i < listeners.size(); i++) {
GestureListener listener = listeners.get(i);
listener.zoom(zoom);
listener.rotate(angle);
listener.move(move);
}
}
public void move(Vector2f move) {
for (int i = 0; i < listeners.size(); i++) {
GestureListener listener = listeners.get(i);
listener.move(move);
}
}
public void zoom(float zoom) {
for (int i = 0; i < listeners.size(); i++) {
GestureListener listener = listeners.get(i);
listener.zoom(zoom);
}
}
private boolean isPosRotation(Vector2f v1, Vector2f v2, float angle) {
float calculationError = 0.01f;
float posRotX = FastMath.cos(FastMath.atan2(v1.y, v1.x)+angle);
float posRotY = FastMath.sin(FastMath.atan2(v1.y, v1.x)+angle);
float divX = FastMath.abs(v2.x - posRotX);
float divY = FastMath.abs(v2.y- posRotY);
if (divX > calculationError || divY > calculationError) {
return true;
}
return false;
}
public void onMove(int cursorId, int xDelta, int yDelta, int x, int y) {
if (cursorId == 1) {
cursor1 = new CursorStore(xDelta, yDelta, x, y);
} else {
cursor2 = new CursorStore(xDelta, yDelta, x, y);
}
}
public void onNew(int cursorId, int x, int y) {
if (cursorId == 1) {
cursor1 = new CursorStore(x, y);
lastCursor1 = null;
} else {
cursor2 = new CursorStore(x, y);
lastCursor2 = null;
}
}
public void onRelease(int cursorId) {
if (cursorId == 1) {
lastCursor1 = cursor1;
cursor1 = null;
} else {
lastCursor2 = cursor2;
cursor2 = null;
}
}
public void addListener( GestureListener listener ) {
if ( listeners == null ) {
listeners = new ArrayList<GestureListener>();
}
listeners.add( listener );
}
/**
* Unsubscribe a listener. Disable event generation if no more listeners.
*
* @param listener to be unsubscribed
*/
public void removeListener( GestureListener listener ) {
if ( listeners != null ) {
listeners.remove( listener );
if (listeners.size() == 0) listeners = null;
}
}
/**
* Remove all listeners and disable event generation.
*/
public void removeListeners() {
if ( listeners != null ) {
listeners.clear();
listeners = null;
}
}
}