/***********************************************************************
* mt4j Copyright (c) 2008 - 2009 Christopher 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.lassoProcessor;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import org.mt4j.components.MTCanvas;
import org.mt4j.components.MTComponent;
import org.mt4j.components.StateChange;
import org.mt4j.components.StateChangeEvent;
import org.mt4j.components.StateChangeListener;
import org.mt4j.components.visibleComponents.shapes.AbstractShape;
import org.mt4j.components.visibleComponents.shapes.MTPolygon;
import org.mt4j.components.visibleComponents.shapes.MTStencilPolygon;
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.input.inputProcessors.componentProcessors.dragProcessor.DragProcessor;
import org.mt4j.input.inputProcessors.componentProcessors.rotateProcessor.RotateProcessor;
import org.mt4j.input.inputProcessors.componentProcessors.scaleProcessor.ScaleProcessor;
import org.mt4j.input.inputProcessors.componentProcessors.tapProcessor.TapProcessor;
import org.mt4j.util.MTColor;
import org.mt4j.util.camera.Icamera;
import org.mt4j.util.math.Ray;
import org.mt4j.util.math.Tools3D;
import org.mt4j.util.math.ToolsGeometry;
import org.mt4j.util.math.Vector3D;
import org.mt4j.util.math.Vertex;
import processing.core.PApplet;
/**
* The Class LassoProcessor. This gesture processor should only be
* registered with a MTCanvas component.
* Fires LassoEvent gesture events.
*
* @author Christopher Ruff
*/
public class LassoProcessor extends AbstractCursorProcessor {
/** The pa. */
private PApplet pa;
/** The canvas. */
private MTCanvas canvas;
/** The cursor to context. */
private Hashtable<InputCursor, ClusteringContext> cursorToContext;
/** The drag selectables. */
private List<IdragClusterable> dragSelectables;
/** The camera. */
private Icamera camera;
/** The plane normal. */
private Vector3D planeNormal;
/** The point in plane. */
private Vector3D pointInPlane;
/**
* Instantiates a new lasso processor.
*
* @param pa the pa
* @param canvas the canvas
* @param camera the camera
*/
public LassoProcessor(PApplet pa, MTCanvas canvas, Icamera camera) {
super();
this.pa = pa;
this.canvas = canvas;
this.camera = camera;
this.dragSelectables = new ArrayList<IdragClusterable>();
cursorToContext = new Hashtable<InputCursor, ClusteringContext>();
planeNormal = new Vector3D(0,0,1);
pointInPlane = new Vector3D(0,0,0);
this.setLockPriority(1);
}
/* (non-Javadoc)
* @see org.mt4j.input.inputProcessors.componentProcessors.AbstractCursorProcessor#cursorStarted(org.mt4j.input.inputData.InputCursor, org.mt4j.input.inputData.AbstractCursorInputEvt)
*/
@Override
public void cursorStarted(InputCursor m, MTFingerInputEvt positionEvent) {
if (this.canLock(m)){
ClusteringContext context = new ClusteringContext(m);
if (!context.gestureAborted){
cursorToContext.put(m, context);
//To speed things up, selection is only checked at the end of the gesture
IdragClusterable[] selectedComps = new IdragClusterable[0]; //no things selected anyway yet
this.fireGestureEvent(new LassoEvent(this,MTGestureEvent.GESTURE_DETECTED, canvas, m, context.getPolygon(), selectedComps));
}
}
}
/* (non-Javadoc)
* @see org.mt4j.input.inputProcessors.componentProcessors.AbstractCursorProcessor#cursorUpdated(org.mt4j.input.inputData.InputCursor, org.mt4j.input.inputData.AbstractCursorInputEvt)
*/
@Override
public void cursorUpdated(InputCursor m, MTFingerInputEvt positionEvent) {
ClusteringContext context = cursorToContext.get(m);
if (context != null){ //cursor was used here
if (!context.gestureAborted){
context.update(m);
//TODO visually mark selected cards and give back real selected cards again..
IdragClusterable[] selectedComps = new IdragClusterable[0];
this.fireGestureEvent(new LassoEvent(this,MTGestureEvent.GESTURE_UPDATED, canvas, m, context.getPolygon(), selectedComps));
}
}
}
/* (non-Javadoc)
* @see org.mt4j.input.inputProcessors.componentProcessors.AbstractCursorProcessor#cursorEnded(org.mt4j.input.inputData.InputCursor, org.mt4j.input.inputData.AbstractCursorInputEvt)
*/
@Override
public void cursorEnded(InputCursor m, MTFingerInputEvt positionEvent) {
logger.debug(this.getName() + " INPUT_ENDED RECIEVED - MOTION: " + m.getId());
ClusteringContext context = cursorToContext.get(m);
if (context != null){ //cursor was used here
cursorToContext.remove(m);
IdragClusterable[] selectedComps = context.getselectedComps();
this.fireGestureEvent(new LassoEvent(this,MTGestureEvent.GESTURE_ENDED, canvas, m, context.getPolygon(), selectedComps));
this.unLock(m);
}
}
/* (non-Javadoc)
* @see org.mt4j.input.inputAnalyzers.IInputAnalyzer#cursorLocked(org.mt4j.input.inputData.InputCursor, org.mt4j.input.inputAnalyzers.IInputAnalyzer)
*/
@Override
public void cursorLocked(InputCursor m, IInputProcessor lockingAnalyzer) {
if (lockingAnalyzer instanceof AbstractComponentProcessor){
logger.debug(this.getName() + " Recieved MOTION LOCKED by (" + ((AbstractComponentProcessor)lockingAnalyzer).getName() + ") - cursor ID: " + m.getId());
}else{
logger.debug(this.getName() + " Recieved MOTION LOCKED by higher priority signal - cursor ID: " + m.getId());
}
this.abortGesture(m);
}
/* (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());
//Do nothing here, we dont want this gesture to be resumable
}
/**
* Abort gesture.
*
* @param m the involved cursor
*/
public void abortGesture(InputCursor m){
ClusteringContext context = cursorToContext.get(m);
if (context != null){ //cursor was used here
cursorToContext.remove(m);
context.update(m);
//because of aborting we send an empty selectionarrray
IdragClusterable[] selectedComps = new IdragClusterable[0];
this.fireGestureEvent(new LassoEvent(this,MTGestureEvent.GESTURE_ENDED, canvas, m, context.getPolygon(), selectedComps));
logger.debug(this.getName() + " cursor:" + m.getId() + " MOTION LOCKED. Was an active cursor in this gesture!");
}else{
logger.debug(this.getName() + " MOTION LOCKED. But it was NOT an active cursor in this gesture!");
}
}
/**
* Adds the clusterable.
*
* @param selectable the selectable
*/
public synchronized void addClusterable(IdragClusterable selectable){
dragSelectables.add(selectable);
if (selectable instanceof MTComponent) {
MTComponent baseComp = (MTComponent) selectable;
baseComp.addStateChangeListener(StateChange.COMPONENT_DESTROYED, new StateChangeListener(){
public void stateChanged(StateChangeEvent evt) {
if (evt.getSource() instanceof IdragClusterable) {
IdragClusterable clusterAble = (IdragClusterable) evt.getSource();
removeClusterable(clusterAble);
//logger.debug("Removed comp from clustergesture analyzers tracking");
}
}
});
}
}
/**
* Removes the clusterable.
*
* @param selectable the selectable
*/
public synchronized void removeClusterable(IdragClusterable selectable){
dragSelectables.remove(selectable);
}
/**
* Gets the tracked selectables.
*
* @return the tracked selectables
*/
public IdragClusterable[] getTrackedSelectables(){
return (IdragClusterable[])dragSelectables.toArray(new IdragClusterable[this.dragSelectables.size()]);
}
/**
* The Class ClusteringContext.
*
* @author Besitzer
*/
private class ClusteringContext{
/** The polygon. */
private MTStencilPolygon polygon;
/** The last position. */
private Vector3D lastPosition;
/** The new position. */
private Vector3D newPosition;
/** The cursor. */
private InputCursor cursor;
/** The selected comps. */
private ArrayList<IdragClusterable> selectedComps;
/** The gesture aborted. */
protected boolean gestureAborted;
/**
* Instantiates a new clustering context.
*
* @param cursor the cursor
*/
public ClusteringContext(InputCursor cursor) {
gestureAborted = false;
this.cursor = cursor;
Vector3D newPos = ToolsGeometry.getRayPlaneIntersection(
Tools3D.getCameraPickRay(pa, camera, cursor.getCurrentEvent().getPosX(), cursor.getCurrentEvent().getPosY()),
planeNormal,
pointInPlane);
if (newPos == null){
logger.error(getName() + " intersection with plane was null in class: " + this.getClass().getName());
gestureAborted = true;
abortGesture(cursor);
return;
}
this.newPosition = newPos;
this.lastPosition = newPos;
// polygon = new MTPolygon(
// new Vertex[]{
// new Vertex(newPos.getX(), newPos.getY(), newPos.getZ()),
// new Vertex(newPos.getX()+0.1f, newPos.getY(), newPos.getZ()),
// new Vertex(newPos.getX(), newPos.getY()+0.1f, newPos.getZ()),
// new Vertex(newPos.getX(), newPos.getY(), newPos.getZ())},
// pa);
polygon = new MTStencilPolygon(
new Vertex[]{
new Vertex(newPos.getX(), newPos.getY(), newPos.getZ()),
new Vertex(newPos.getX()+0.1f, newPos.getY(), newPos.getZ()),
new Vertex(newPos.getX(), newPos.getY()+0.1f, newPos.getZ()),
new Vertex(newPos.getX(), newPos.getY(), newPos.getZ())},
pa);
polygon.setPickable(true);
polygon.setNoStroke(false);
polygon.setNoFill(false);
polygon.setFillColor(new MTColor(100, 150, 250, 55));
// polygon.setStrokeColor(150,150,250,255);
polygon.setStrokeColor(new MTColor(0,0,0,255));
polygon.setStrokeWeight(1.5f);
polygon.setDrawSmooth(true);
polygon.setUseDirectGL(true);
polygon.setLineStipple((short)0xBBBB);
polygon.setName("SelectPoly");
polygon.setGestureAllowance(RotateProcessor.class, false);
polygon.setGestureAllowance(ScaleProcessor.class, false);
polygon.setGestureAllowance(TapProcessor.class, false);
polygon.setGestureAllowance(DragProcessor.class, false);
polygon.setBoundsAutoCompute(false);
polygon.setBoundsBehaviour(AbstractShape.BOUNDS_DONT_USE);
// polygon.setComposite(true);
selectedComps = new ArrayList<IdragClusterable>();
}
/**
* Gets the selected comps.
*
* @return the selected comps
*/
public IdragClusterable[] getselectedComps() {
selectedComps.clear();
for (int i = 0; i < dragSelectables.size(); i++) {
IdragClusterable currentCard = dragSelectables.get(i);
Vector3D globCenter = new Vector3D(currentCard.getCenterPointGlobal());
globCenter.setZ(0);
// if (this.getPolygon().containsPointGlobal(currentCard.getCenterPointGlobal())){
if (this.getPolygon().containsPointGlobal(globCenter)){
selectedComps.add(currentCard);
}
}
return (IdragClusterable[])selectedComps.toArray(new IdragClusterable[this.selectedComps.size()]);
}
/**
* Update.
*
* @param cursor the cursor
*/
public void update(InputCursor cursor){
if (!gestureAborted){
lastPosition = newPosition;
// pa.pushMatrix();
// camera.update();
// //Unproject the coords again taking the changed camera into account
// this.newPosition = Tools3D.unprojectScreenCoords(pa, cursor.getLastEvent().getPositionX(), cursor.getLastEvent().getPositionY());
// pa.popMatrix();
this.newPosition = Tools3D.unprojectScreenCoords(pa, camera, cursor.getCurrentEvent().getPosX(), cursor.getCurrentEvent().getPosY());
Vector3D rayStartPoint = camera.getPosition(); //default cam
Vector3D newPos = ToolsGeometry.getRayPlaneIntersection(new Ray(rayStartPoint, newPosition), planeNormal, pointInPlane);
newPosition = newPos;
if (newPosition != null && !lastPosition.equalsVector(newPosition)){
Vertex[] newArr = new Vertex[this.getPolygon().getVertexCount()+1];
Vertex[] polyVertices = this.getPolygon().getVerticesGlobal();
//set the old last point to the next index
System.arraycopy(polyVertices, 0, newArr, 0, this.getPolygon().getVertexCount());
newArr[newArr.length-1] = polyVertices[0]; //close poly correctly
//Create the new vertex
Vertex newVert = new Vertex(newPosition.getX(), newPosition.getY(), newPosition.getZ(), 100,150,250,255);
newVert.setA(120);
newArr[newArr.length-2] = newVert; //set the new value to be the length-2 one
polygon.setVertices(newArr);
}
}
}
/**
* Gets the last position.
*
* @return the last position
*/
public Vector3D getLastPosition() {
return lastPosition;
}
/**
* Gets the cursor.
*
* @return the cursor
*/
public InputCursor getCursor() {
return cursor;
}
/**
* Gets the new position.
*
* @return the new position
*/
public Vector3D getNewPosition() {
return newPosition;
}
/**
* Gets the polygon.
*
* @return the polygon
*/
public MTPolygon getPolygon() {
return polygon;
}
}
/* (non-Javadoc)
* @see org.mt4j.input.inputProcessors.componentProcessors.AbstractComponentProcessor#getName()
*/
@Override
public String getName() {
return "Lasso";
}
}