annotate java/src/uk/ac/qmul/eecs/ccmi/haptics/OmniHaptics.java @ 1:e3935c01cde2 tip

moved license of PdPersistenceManager to the beginning of the file
author Fiore Martin <f.martin@qmul.ac.uk>
date Tue, 08 Jul 2014 19:52:03 +0100
parents 78b7fc5391a2
children
rev   line source
f@0 1 /*
f@0 2 CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
f@0 3
f@0 4 Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
f@0 5
f@0 6 This program is free software: you can redistribute it and/or modify
f@0 7 it under the terms of the GNU General Public License as published by
f@0 8 the Free Software Foundation, either version 3 of the License, or
f@0 9 (at your option) any later version.
f@0 10
f@0 11 This program is distributed in the hope that it will be useful,
f@0 12 but WITHOUT ANY WARRANTY; without even the implied warranty of
f@0 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
f@0 14 GNU General Public License for more details.
f@0 15
f@0 16 You should have received a copy of the GNU General Public License
f@0 17 along with this program. If not, see <http://www.gnu.org/licenses/>.
f@0 18 */
f@0 19
f@0 20 package uk.ac.qmul.eecs.ccmi.haptics;
f@0 21
f@0 22 import java.awt.Dimension;
f@0 23 import java.awt.Toolkit;
f@0 24 import java.awt.geom.Line2D;
f@0 25 import java.io.IOException;
f@0 26 import java.net.URL;
f@0 27 import java.util.ArrayList;
f@0 28 import java.util.BitSet;
f@0 29 import java.util.HashMap;
f@0 30 import java.util.ListIterator;
f@0 31
f@0 32 import uk.ac.qmul.eecs.ccmi.utils.ResourceFileWriter;
f@0 33 import uk.ac.qmul.eecs.ccmi.utils.OsDetector;
f@0 34 import uk.ac.qmul.eecs.ccmi.utils.PreferencesService;
f@0 35
f@0 36 /*
f@0 37 *
f@0 38 * The implementation of the Haptics interface which uses SensableŽ
f@0 39 * PHANTOM OmniŽ haptic device.
f@0 40 *
f@0 41 */
f@0 42 class OmniHaptics extends Thread implements Haptics {
f@0 43
f@0 44 static Haptics createInstance(HapticListenerThread listener) {
f@0 45 if(listener == null)
f@0 46 throw new IllegalArgumentException("listener cannot be null");
f@0 47
f@0 48 if(OsDetector.has64BitJVM()){
f@0 49 return null;// no 64 native library supported yet
f@0 50 }
f@0 51
f@0 52 if(OsDetector.isWindows()){
f@0 53 /* try to load .dll. First copy it in the home/ccmi_editor_data/lib directory */
f@0 54 URL url = OmniHaptics.class.getResource("OmniHaptics.dll");
f@0 55 ResourceFileWriter fileWriter = new ResourceFileWriter(url);
f@0 56 fileWriter.writeOnDisk(
f@0 57 PreferencesService.getInstance().get("dir.libs", System.getProperty("java.io.tmpdir")),
f@0 58 "OmniHaptics.dll");
f@0 59 String path = fileWriter.getFilePath();
f@0 60 try{
f@0 61 if(path == null)
f@0 62 throw new UnsatisfiedLinkError("OmniHaptics.dll missing");
f@0 63 System.load( path );
f@0 64 }catch(UnsatisfiedLinkError e){
f@0 65 e.printStackTrace();
f@0 66 return null;
f@0 67 }
f@0 68 }else{
f@0 69 return null;
f@0 70 }
f@0 71
f@0 72 OmniHaptics omniHaptics = new OmniHaptics("Haptics");
f@0 73 omniHaptics.hapticListener = listener;
f@0 74 /* start up the listener which immediately stops, waiting for commands */
f@0 75 if(!omniHaptics.hapticListener.isAlive())
f@0 76 omniHaptics.hapticListener.start();
f@0 77 /* start up the haptics thread which issues commands from the java to the c++ thread */
f@0 78 omniHaptics.start();
f@0 79 /* wait for the haptics thread (now running native code) to initialize (need to know if initialization is successful) */
f@0 80 synchronized(omniHaptics){
f@0 81 try {
f@0 82 omniHaptics.wait();
f@0 83 }catch (InterruptedException ie) {
f@0 84 throw new RuntimeException(ie); // must never happen
f@0 85 }
f@0 86 }
f@0 87 if(omniHaptics.hapticInitFailed){
f@0 88 /* the initialization has failed, the haptic thread is about to die */
f@0 89 /* don't kill the listener as initialization will be tried on other devices */
f@0 90 // while(!omniHaptics.hapticListener.isInterrupted()){
f@0 91 // omniHaptics.hapticListener.interrupt();
f@0 92 // }
f@0 93 // omniHaptics.hapticListener = null; //leave the listener to the GC
f@0 94 return null;
f@0 95 }else{
f@0 96 return omniHaptics;
f@0 97 }
f@0 98 }
f@0 99
f@0 100 private OmniHaptics(String threadName){
f@0 101 super(threadName);
f@0 102 /* get the screen size which will be passed to init methos in order to set up a window
f@0 103 * for the haptic with the same size as the swing one
f@0 104 */
f@0 105 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
f@0 106
f@0 107 int screenWidth = (int)screenSize.getWidth();
f@0 108 int screenHeight = (int)screenSize.getHeight();
f@0 109
f@0 110 width = screenWidth * 5 / 8;
f@0 111 height = screenHeight * 5 / 8;
f@0 112
f@0 113 newHapticId = false;
f@0 114 dumpHapticId = false;
f@0 115 shutdown = false;
f@0 116 hapticInitFailed = false;
f@0 117 nodes = new HashMap<String,ArrayList<Node>>();
f@0 118 edges = new HashMap<String,ArrayList<Edge>>();
f@0 119 currentNodes = Empties.EMPTY_NODE_LIST;
f@0 120 currentEdges = Empties.EMPTY_EDGE_LIST;
f@0 121 nodesMaps = new HashMap<String,HashMap<Integer,Node>>();
f@0 122 edgesMaps = new HashMap<String,HashMap<Integer,Edge>>();
f@0 123 currentNodesMap = Empties.EMPTY_NODE_MAP;
f@0 124 currentEdgesMap = Empties.EMPTY_EDGE_MAP;
f@0 125 }
f@0 126
f@0 127
f@0 128 public native int initOmni(int width, int height) throws IOException;
f@0 129
f@0 130 @Override
f@0 131 public void addNewDiagram(String diagramName){
f@0 132 ArrayList<Node> cNodes = new ArrayList<Node>(30);
f@0 133 ArrayList<Edge> cEdges = new ArrayList<Edge>(30);
f@0 134 nodes.put(diagramName, cNodes);
f@0 135 edges.put(diagramName, cEdges);
f@0 136
f@0 137 HashMap<Integer,Node> cNodesMap = new HashMap<Integer,Node>();
f@0 138 HashMap<Integer,Edge> cEdgesMap = new HashMap<Integer,Edge>();
f@0 139 nodesMaps.put(diagramName, cNodesMap);
f@0 140 edgesMaps.put(diagramName, cEdgesMap);
f@0 141
f@0 142 synchronized(this){
f@0 143 currentNodes = cNodes;
f@0 144 currentEdges = cEdges;
f@0 145 currentNodesMap = cNodesMap;
f@0 146 currentEdgesMap = cEdgesMap;
f@0 147 }
f@0 148 }
f@0 149
f@0 150 @Override
f@0 151 public synchronized void switchDiagram(String diagramName){
f@0 152 // check nodes only, as the edges and nodes maps are strongly coupled
f@0 153 if(!nodes.containsKey(diagramName))
f@0 154 throw new IllegalArgumentException("Diagram not found among added diagrams:" + diagramName);
f@0 155
f@0 156 currentNodes = nodes.get(diagramName);
f@0 157 currentEdges = edges.get(diagramName);
f@0 158 currentNodesMap = nodesMaps.get(diagramName);
f@0 159 currentEdgesMap = edgesMaps.get(diagramName);
f@0 160 }
f@0 161
f@0 162 @Override
f@0 163 public synchronized void removeDiagram(String diagramNameToRemove, String diagramNameNext){
f@0 164 if(!nodes.containsKey(diagramNameToRemove))
f@0 165 throw new IllegalArgumentException("Diagram not found among added diagrams:" + diagramNameToRemove);
f@0 166
f@0 167 nodes.remove(diagramNameToRemove);
f@0 168 edges.remove(diagramNameToRemove);
f@0 169 nodesMaps.remove(diagramNameToRemove);
f@0 170 edgesMaps.remove(diagramNameToRemove);
f@0 171 if(diagramNameNext == null){
f@0 172 currentNodes = Empties.EMPTY_NODE_LIST;
f@0 173 currentEdges = Empties.EMPTY_EDGE_LIST;
f@0 174 currentNodesMap = Empties.EMPTY_NODE_MAP;
f@0 175 currentEdgesMap = Empties.EMPTY_EDGE_MAP;
f@0 176 }else{
f@0 177 if(!nodes.containsKey(diagramNameNext))
f@0 178 throw new IllegalArgumentException("Diagram not found among added diagrams:" + diagramNameNext);
f@0 179 currentNodes = nodes.get(diagramNameNext);
f@0 180 currentEdges = edges.get(diagramNameNext);
f@0 181 currentNodesMap = nodesMaps.get(diagramNameNext);
f@0 182 currentEdgesMap = edgesMaps.get(diagramNameNext);
f@0 183 }
f@0 184 }
f@0 185
f@0 186 @Override
f@0 187 public synchronized void addNode(double x, double y, int nodeHashCode, String diagramName){
f@0 188 newHapticId = true;
f@0 189 // waits for an identifier from the openGL thread
f@0 190 try{
f@0 191 wait();
f@0 192 }catch(InterruptedException ie){
f@0 193 wasInterrupted();
f@0 194 }
f@0 195
f@0 196 Node n = new Node(x,y,nodeHashCode, currentHapticId);
f@0 197 if(diagramName == null){
f@0 198 currentNodes.add(n);
f@0 199 currentNodesMap.put(currentHapticId, n);
f@0 200 }else{
f@0 201 nodes.get(diagramName).add(n);
f@0 202 nodesMaps.get(diagramName).put(currentHapticId, n);
f@0 203 }
f@0 204 }
f@0 205
f@0 206 @Override
f@0 207 public synchronized void removeNode(int nodeHashCode, String diagramName){
f@0 208 ListIterator<Node> itr = (diagramName == null) ? currentNodes.listIterator() : nodes.get(diagramName).listIterator();
f@0 209 boolean found = false;
f@0 210 int hID = -1;
f@0 211 while(itr.hasNext()){
f@0 212 Node n = itr.next();
f@0 213 if(n.diagramId == nodeHashCode){
f@0 214 hID = n.hapticId;
f@0 215 itr.remove();
f@0 216 found = true;
f@0 217 break;
f@0 218 }
f@0 219 }
f@0 220 assert(found);
f@0 221
f@0 222 /* remove the node from the map as well */
f@0 223 if(diagramName == null)
f@0 224 currentNodesMap.remove(hID);
f@0 225 else
f@0 226 nodesMaps.get(diagramName).remove(hID);
f@0 227
f@0 228 /* set the flag to ask the haptic thread to free the id of the node that's been deleted */
f@0 229 dumpHapticId = true;
f@0 230 /* share the id to free with the other thread */
f@0 231 currentHapticId = hID;
f@0 232 try{
f@0 233 wait();
f@0 234 }catch(InterruptedException ie){
f@0 235 wasInterrupted();
f@0 236 }
f@0 237 }
f@0 238
f@0 239 @Override
f@0 240 public synchronized void moveNode(double x, double y, int nodeHashCode, String diagramName){
f@0 241 ArrayList<Node> iterationList = (diagramName == null) ? currentNodes : nodes.get(diagramName);
f@0 242 for(Node n : iterationList){
f@0 243 if(n.diagramId == nodeHashCode){
f@0 244 n.x = x;
f@0 245 n.y = y;
f@0 246 break;
f@0 247 }
f@0 248 }
f@0 249 }
f@0 250
f@0 251 @Override
f@0 252 public synchronized void addEdge(int edgeHashCode, double[] xs, double[] ys, BitSet[] adjMatrix, int nodeStart, int stipplePattern, Line2D attractLine, String diagramName){
f@0 253 // flag the openGL thread the fact we need an identifier
f@0 254 newHapticId = true;
f@0 255 // waits for an identifier from the openGL thread
f@0 256 try{
f@0 257 wait();
f@0 258 }catch(InterruptedException ie){
f@0 259 wasInterrupted();
f@0 260 }
f@0 261
f@0 262 // find the mid point of the line of attraction
f@0 263 double pX = Math.min(attractLine.getX1(), attractLine.getX2());
f@0 264 double pY = Math.min(attractLine.getY1(), attractLine.getY2());
f@0 265 pX += Math.abs(attractLine.getX1() - attractLine.getX2())/2;
f@0 266 pY += Math.abs(attractLine.getY1() - attractLine.getY2())/2;
f@0 267
f@0 268 Edge e = new Edge(edgeHashCode,currentHapticId, xs, ys, adjMatrix, nodeStart, stipplePattern, pX, pY);
f@0 269 if(diagramName == null){
f@0 270 /* add the edge reference to the Haptic edges list */
f@0 271 currentEdges.add(e);
f@0 272 /* add the edge reference to the Haptic edges map */
f@0 273 currentEdgesMap.put(currentHapticId, e);
f@0 274 }else{
f@0 275 /* add the edge reference to the Haptic edges list */
f@0 276 edges.get(diagramName).add(e);
f@0 277 /* add the edge reference to the Haptic edges map */
f@0 278 edgesMaps.get(diagramName).put(currentHapticId, e);
f@0 279 }
f@0 280 }
f@0 281
f@0 282 @Override
f@0 283 public synchronized void updateEdge(int edgeHashCode, double[] xs, double[] ys, BitSet[] adjMatrix, int nodeStart, Line2D attractLine, String diagramName){
f@0 284 assert(xs.length == ys.length);
f@0 285
f@0 286 for(Edge e : currentEdges){
f@0 287 if(e.diagramId == edgeHashCode){
f@0 288 e.xs = xs;
f@0 289 e.ys = ys;
f@0 290 e.size = xs.length;
f@0 291 e.adjMatrix = adjMatrix;
f@0 292 e.nodeStart = nodeStart;
f@0 293 // find the mid point of the line of attraction
f@0 294 double pX = Math.min(attractLine.getX1(), attractLine.getX2());
f@0 295 double pY = Math.min(attractLine.getY1(), attractLine.getY2());
f@0 296 pX += Math.abs(attractLine.getX1() - attractLine.getX2())/2;
f@0 297 pY += Math.abs(attractLine.getY1() - attractLine.getY2())/2;
f@0 298 e.attractPointX = pX;
f@0 299 e.attractPointY = pY;
f@0 300 }
f@0 301 }
f@0 302 }
f@0 303
f@0 304 @Override
f@0 305 public synchronized void removeEdge(int edgeHashCode, String diagramName){
f@0 306 ListIterator<Edge> itr = (diagramName == null) ? currentEdges.listIterator() : edges.get(diagramName).listIterator();
f@0 307 boolean found = false;
f@0 308 int hID = -1;
f@0 309 while(itr.hasNext()){
f@0 310 Edge e = itr.next();
f@0 311 if(e.diagramId == edgeHashCode){
f@0 312 hID = e.hapticId;
f@0 313 itr.remove();
f@0 314 found = true;
f@0 315 break;
f@0 316 }
f@0 317 }
f@0 318 assert(found);
f@0 319
f@0 320 /* remove the edge from the map as well */
f@0 321 if(diagramName == null)
f@0 322 currentEdgesMap.remove(hID);
f@0 323 else
f@0 324 edgesMaps.get(diagramName).remove(hID);
f@0 325 /* set the flag to ask the haptic thread to free the id of the node that's been deleted */
f@0 326 dumpHapticId = true;
f@0 327 /* share the id to free with the other thread */
f@0 328 currentHapticId = hID;
f@0 329 try{
f@0 330 wait();
f@0 331 }catch(InterruptedException ie){
f@0 332 wasInterrupted();
f@0 333 }
f@0 334 }
f@0 335
f@0 336 @Override
f@0 337 public synchronized void attractTo(int elementHashCode){
f@0 338 attractToHapticId = findElementHapticID(elementHashCode);
f@0 339 attractTo = true;
f@0 340 }
f@0 341
f@0 342 @Override
f@0 343 public synchronized void pickUp(int elementHashCode){
f@0 344 pickUpHapticId = findElementHapticID(elementHashCode);
f@0 345 pickUp = true;
f@0 346 }
f@0 347
f@0 348 private int findElementHapticID(int elementHashCode){
f@0 349 int hID = -1;
f@0 350 boolean found = false;
f@0 351 for(Node n : currentNodes){
f@0 352 if(n.diagramId == elementHashCode){
f@0 353 hID = n.hapticId;
f@0 354 found = true;
f@0 355 break;
f@0 356 }
f@0 357 }
f@0 358
f@0 359 if(!found)
f@0 360 for(Edge e : currentEdges){
f@0 361 if(e.diagramId == elementHashCode){
f@0 362 hID = e.hapticId;
f@0 363 found = true;
f@0 364 break;
f@0 365 }
f@0 366 }
f@0 367 assert(found);
f@0 368 return hID;
f@0 369 }
f@0 370
f@0 371 @Override
f@0 372 public void setVisible(boolean visible){
f@0 373 // not implemented but required by Haptic interface
f@0 374 }
f@0 375
f@0 376 @Override
f@0 377 public synchronized void dispose(){
f@0 378 shutdown = true;
f@0 379 /* wait for the haptic thread to shut down */
f@0 380 try {
f@0 381 wait();
f@0 382 } catch (InterruptedException e) {
f@0 383 wasInterrupted();
f@0 384 }
f@0 385 }
f@0 386
f@0 387 @Override
f@0 388 public void run() {
f@0 389 try {
f@0 390 initOmni(width,height);
f@0 391 } catch (IOException e) {
f@0 392 throw new RuntimeException();// OMNI haptic device doesn't cause any exception
f@0 393 }
f@0 394 }
f@0 395
f@0 396 private void wasInterrupted(){
f@0 397 throw new UnsupportedOperationException("Haptics thread interrupted and no catch block implemented");
f@0 398 }
f@0 399
f@0 400 private Node getNodeFromID(int hID){
f@0 401 return currentNodesMap.get(hID);
f@0 402 }
f@0 403
f@0 404 private Edge getEdgeFromID(int hID){
f@0 405 return currentEdgesMap.get(hID);
f@0 406 }
f@0 407
f@0 408 /* the diagram currently selected */
f@0 409 private ArrayList<Node> currentNodes;
f@0 410 private ArrayList<Edge> currentEdges;
f@0 411 private HashMap<Integer,Node> currentNodesMap;
f@0 412 private HashMap<Integer,Edge> currentEdgesMap;
f@0 413
f@0 414 /* maps with all the diagrams in the editor */
f@0 415 private HashMap<String,ArrayList<Node>> nodes;
f@0 416 private HashMap<String,ArrayList<Edge>> edges;
f@0 417 private HashMap<String,HashMap<Integer,Node>> nodesMaps;
f@0 418 private HashMap<String,HashMap<Integer,Edge>> edgesMaps;
f@0 419 private int width;
f@0 420 private int height;
f@0 421 private int attractToHapticId;
f@0 422 private int pickUpHapticId;
f@0 423 /* flag for synchronization with the haptic thread*/
f@0 424 private boolean newHapticId;
f@0 425 private boolean dumpHapticId;
f@0 426 private boolean shutdown;
f@0 427 boolean hapticInitFailed;
f@0 428 private boolean attractTo;
f@0 429 private boolean pickUp;
f@0 430 /* currentHapticId is used to share haptic ids between the threads */
f@0 431 private int currentHapticId;
f@0 432 private HapticListenerThread hapticListener;
f@0 433
f@0 434 }