/*********************************************************************** * 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"; } }