Mercurial > hg > ccmieditor
diff java/src/uk/ac/qmul/eecs/ccmi/haptics/OmniHaptics.java @ 3:9e67171477bc
PHANTOM Omni Heptic device release
author | Fiore Martin <fiore@eecs.qmul.ac.uk> |
---|---|
date | Wed, 25 Apr 2012 17:09:09 +0100 |
parents | |
children | d66dd5880081 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/OmniHaptics.java Wed Apr 25 17:09:09 2012 +0100 @@ -0,0 +1,431 @@ +/* + CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool + + Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/) + + 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 uk.ac.qmul.eecs.ccmi.haptics; + +import java.awt.Dimension; +import java.awt.Toolkit; +import java.awt.geom.Line2D; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashMap; +import java.util.ListIterator; + +import uk.ac.qmul.eecs.ccmi.utils.ResourceFileWriter; +import uk.ac.qmul.eecs.ccmi.utils.OsDetector; +import uk.ac.qmul.eecs.ccmi.utils.PreferencesService; + +/* + * + * The implementation of the Haptics interface which uses SensableŽ + * PHANTOM OmniŽ haptic device. + * + */ +class OmniHaptics extends Thread implements Haptics { + + static Haptics createInstance(HapticListener listener) { + if(listener == null) + throw new IllegalArgumentException("listener cannot be null"); + + if(OsDetector.isWindows()){ + /* try to load .dll. First copy it in the home/ccmi_editor_data/lib directory */ + URL url = OmniHaptics.class.getResource("Haptics.dll"); + ResourceFileWriter fileWriter = new ResourceFileWriter(url); + fileWriter.writeToDisk( + PreferencesService.getInstance().get("dir.libs", System.getProperty("java.io.tmpdir")), + "Haptics.dll"); + String path = fileWriter.getFilePath(); + if(path == null) + return null; + try{ + System.load( path ); + }catch(UnsatisfiedLinkError e){ + return null; + } + }else{ + return null; + } + + OmniHaptics omniHaptics = new OmniHaptics("Haptics"); + omniHaptics.hapticListener = listener; + /* start up the listener which immediately stops, waiting for commands */ + if(!omniHaptics.hapticListener.isAlive()) + omniHaptics.hapticListener.start(); + /* start up the haptics thread which issues commands from the java to the c++ thread */ + omniHaptics.start(); + /* wait for the haptics thread (now running native code) to initialize (need to know if initialization is successful) */ + synchronized(omniHaptics){ + try { + omniHaptics.wait(); + }catch (InterruptedException ie) { + throw new RuntimeException(ie); // must never happen + } + } + if(omniHaptics.hapticInitFailed){ + /* the initialization has failed, the haptic thread is about to die */ + while(!omniHaptics.hapticListener.isInterrupted()){ + omniHaptics.hapticListener.interrupt(); + } + omniHaptics.hapticListener = null; //leave the listener to the GC + return null; + }else{ + return omniHaptics; + } + } + + private OmniHaptics(String threadName){ + super(threadName); + /* get the screen size which will be passed to init methos in order to set up a window + * for the haptic with the same size as the swing one + */ + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + + int screenWidth = (int)screenSize.getWidth(); + int screenHeight = (int)screenSize.getHeight(); + + width = screenWidth * 5 / 8; + height = screenHeight * 5 / 8; + + newHapticId = false; + dumpHapticId = false; + shutdown = false; + hapticInitFailed = false; + nodes = new HashMap<String,ArrayList<Node>>(); + edges = new HashMap<String,ArrayList<Edge>>(); + currentNodes = EMPTY_NODE_LIST; + currentEdges = EMPTY_EDGE_LIST; + nodesMaps = new HashMap<String,HashMap<Integer,Node>>(); + edgesMaps = new HashMap<String,HashMap<Integer,Edge>>(); + currentNodesMap = EMPTY_NODE_MAP; + currentEdgesMap = EMPTY_EDGE_MAP; + } + + + @Override + public native int init(int width, int height) throws IOException; + + @Override + public void addNewDiagram(String name, boolean switchAfter){ + ArrayList<Node> cNodes = new ArrayList<Node>(30); + ArrayList<Edge> cEdges = new ArrayList<Edge>(30); + nodes.put(name, cNodes); + edges.put(name, cEdges); + + HashMap<Integer,Node> cNodesMap = new HashMap<Integer,Node>(); + HashMap<Integer,Edge> cEdgesMap = new HashMap<Integer,Edge>(); + nodesMaps.put(name, cNodesMap); + edgesMaps.put(name, cEdgesMap); + + if(switchAfter){ + synchronized(this){ + currentNodes = cNodes; + currentEdges = cEdges; + currentNodesMap = cNodesMap; + currentEdgesMap = cEdgesMap; + } + } + } + + @Override + public synchronized void switchDiagram(String diagramName){ + // check nodes only, as the edges and nodes maps are strongly coupled + if(!nodes.containsKey(diagramName)) + throw new IllegalArgumentException("Diagram " + diagramName + " not present among the current ones"); + + currentNodes = nodes.get(diagramName); + currentEdges = edges.get(diagramName); + currentNodesMap = nodesMaps.get(diagramName); + currentEdgesMap = edgesMaps.get(diagramName); + } + + @Override + public synchronized void removeDiagram(String diagramNameToRemove, String diagramNameNext){ + if(!nodes.containsKey(diagramNameToRemove)) + throw new IllegalArgumentException("Id " + diagramNameToRemove + " not present aong the current ones"); + + nodes.remove(diagramNameToRemove); + edges.remove(diagramNameToRemove); + nodesMaps.remove(diagramNameToRemove); + edgesMaps.remove(diagramNameToRemove); + if(diagramNameNext == null){ + currentNodes = EMPTY_NODE_LIST; + currentEdges = EMPTY_EDGE_LIST; + currentNodesMap = EMPTY_NODE_MAP; + currentEdgesMap = EMPTY_EDGE_MAP; + }else{ + if(!nodes.containsKey(diagramNameNext)) + throw new IllegalArgumentException("Id " + diagramNameNext + " not present aong the current ones"); + currentNodes = nodes.get(diagramNameNext); + currentEdges = edges.get(diagramNameNext); + currentNodesMap = nodesMaps.get(diagramNameNext); + currentEdgesMap = edgesMaps.get(diagramNameNext); + } + } + + @Override + public synchronized void addNode(double x, double y, int nodeHashCode, String diagramName){ + newHapticId = true; + // waits for an identifier from the openGL thread + try{ + wait(); + }catch(InterruptedException ie){ + wasInterrupted(); + } + + Node n = new Node(x,y,nodeHashCode, currentHapticId); + if(diagramName == null){ + currentNodes.add(n); + currentNodesMap.put(currentHapticId, n); + }else{ + nodes.get(diagramName).add(n); + nodesMaps.get(diagramName).put(currentHapticId, n); + } + } + + @Override + public synchronized void removeNode(int nodeHashCode, String diagramName){ + ListIterator<Node> itr = (diagramName == null) ? currentNodes.listIterator() : nodes.get(diagramName).listIterator(); + boolean found = false; + int hID = -1; + while(itr.hasNext()){ + Node n = itr.next(); + if(n.diagramId == nodeHashCode){ + hID = n.hapticId; + itr.remove(); + found = true; + break; + } + } + assert(found); + + /* remove the node from the map as well */ + if(diagramName == null) + currentNodesMap.remove(hID); + else + nodesMaps.get(diagramName).remove(hID); + + /* set the flag to ask the haptic thread to free the id of the node that's been deleted */ + dumpHapticId = true; + /* share the id to free with the other thread */ + currentHapticId = hID; + try{ + wait(); + }catch(InterruptedException ie){ + wasInterrupted(); + } + } + + @Override + public synchronized void moveNode(double x, double y, int nodeHashCode, String diagramName){ + ArrayList<Node> iterationList = (diagramName == null) ? currentNodes : nodes.get(diagramName); + for(Node n : iterationList){ + if(n.diagramId == nodeHashCode){ + n.x = x; + n.y = y; + break; + } + } + } + + @Override + public synchronized void addEdge(int edgeHashCode, double[] xs, double[] ys, BitSet[] adjMatrix, int nodeStart, int stipplePattern, Line2D attractLine, String diagramName){ + // flag the openGL thread the fact we need an identifier + newHapticId = true; + // waits for an identifier from the openGL thread + try{ + wait(); + }catch(InterruptedException ie){ + wasInterrupted(); + } + + // find the mid point of the line of attraction + double pX = Math.min(attractLine.getX1(), attractLine.getX2()); + double pY = Math.min(attractLine.getY1(), attractLine.getY2()); + pX += Math.abs(attractLine.getX1() - attractLine.getX2())/2; + pY += Math.abs(attractLine.getY1() - attractLine.getY2())/2; + + Edge e = new Edge(edgeHashCode,currentHapticId, xs, ys, adjMatrix, nodeStart, stipplePattern, pX, pY); + if(diagramName == null){ + /* add the edge reference to the Haptic edges list */ + currentEdges.add(e); + /* add the edge reference to the haptic edges map */ + currentEdgesMap.put(currentHapticId, e); + }else{ + /* add the edge reference to the Haptic edges list */ + edges.get(diagramName).add(e); + /* add the edge reference to the haptic edges map */ + edgesMaps.get(diagramName).put(currentHapticId, e); + } + } + + @Override + public synchronized void updateEdge(int edgeHashCode, double[] xs, double[] ys, BitSet[] adjMatrix, int nodeStart, Line2D attractLine, String diagramName){ + assert(xs.length == ys.length); + + for(Edge e : currentEdges){ + if(e.diagramId == edgeHashCode){ + e.xs = xs; + e.ys = ys; + e.size = xs.length; + e.adjMatrix = adjMatrix; + e.nodeStart = nodeStart; + // find the mid point of the line of attraction + double pX = Math.min(attractLine.getX1(), attractLine.getX2()); + double pY = Math.min(attractLine.getY1(), attractLine.getY2()); + pX += Math.abs(attractLine.getX1() - attractLine.getX2())/2; + pY += Math.abs(attractLine.getY1() - attractLine.getY2())/2; + e.attractPointX = pX; + e.attractPointY = pY; + } + } + } + + @Override + public synchronized void removeEdge(int edgeHashCode, String diagramName){ + ListIterator<Edge> itr = (diagramName == null) ? currentEdges.listIterator() : edges.get(diagramName).listIterator(); + boolean found = false; + int hID = -1; + while(itr.hasNext()){ + Edge e = itr.next(); + if(e.diagramId == edgeHashCode){ + hID = e.hapticId; + itr.remove(); + found = true; + break; + } + } + assert(found); + + /* remove the edge from the map as well */ + if(diagramName == null) + currentEdgesMap.remove(hID); + else + edgesMaps.get(diagramName).remove(hID); + /* set the flag to ask the haptic thread to free the id of the node that's been deleted */ + dumpHapticId = true; + /* share the id to free with the other thread */ + currentHapticId = hID; + try{ + wait(); + }catch(InterruptedException ie){ + wasInterrupted(); + } + } + + @Override + public synchronized void attractTo(int elementHashCode){ + attractToHapticId = findElementHapticID(elementHashCode); + attractTo = true; + } + + @Override + public synchronized void pickUp(int elementHashCode){ + pickUpHapticId = findElementHapticID(elementHashCode); + pickUp = true; + } + + private int findElementHapticID(int elementHashCode){ + int hID = -1; + boolean found = false; + for(Node n : currentNodes){ + if(n.diagramId == elementHashCode){ + hID = n.hapticId; + found = true; + break; + } + } + + if(!found) + for(Edge e : currentEdges){ + if(e.diagramId == elementHashCode){ + hID = e.hapticId; + found = true; + break; + } + } + assert(found); + return hID; + } + + @Override + public synchronized void dispose(){ + shutdown = true; + /* wait for the haptic thread to shut down */ + try { + wait(); + } catch (InterruptedException e) { + wasInterrupted(); + } + } + + @Override + public void run() { + try { + init(width,height); + } catch (IOException e) { + throw new RuntimeException();// OMNI haptic device doesn't cause any exception + } + } + + private void wasInterrupted(){ + throw new UnsupportedOperationException("Haptics thread interrupted and no catch block implemented"); + } + + private Node getNodeFromID(int hID){ + return currentNodesMap.get(hID); + } + + private Edge getEdgeFromID(int hID){ + return currentEdgesMap.get(hID); + } + + /* the diagram currently selected */ + private ArrayList<Node> currentNodes; + private ArrayList<Edge> currentEdges; + private HashMap<Integer,Node> currentNodesMap; + private HashMap<Integer,Edge> currentEdgesMap; + + /* maps with all the diagrams in the editor */ + private HashMap<String,ArrayList<Node>> nodes; + private HashMap<String,ArrayList<Edge>> edges; + private HashMap<String,HashMap<Integer,Node>> nodesMaps; + private HashMap<String,HashMap<Integer,Edge>> edgesMaps; + private int width; + private int height; + private int attractToHapticId; + private int pickUpHapticId; + /* flag for synchronization with the haptic thread*/ + private boolean newHapticId; + private boolean dumpHapticId; + private boolean shutdown; + boolean hapticInitFailed; + private boolean attractTo; + private boolean pickUp; + /* currentHapticId is used to share haptic ids between the threads */ + private int currentHapticId; + private HapticListener hapticListener; + + private static final ArrayList<Node> EMPTY_NODE_LIST = new ArrayList<Node>(0); + private static final ArrayList<Edge> EMPTY_EDGE_LIST = new ArrayList<Edge>(0); + private static final HashMap<Integer,Node> EMPTY_NODE_MAP = new HashMap<Integer,Node>(); + private static final HashMap<Integer,Edge> EMPTY_EDGE_MAP = new HashMap<Integer,Edge>(); + +}