fiore@5: /* fiore@5: CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool fiore@5: fiore@5: Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/) fiore@5: fiore@5: This program is free software: you can redistribute it and/or modify fiore@5: it under the terms of the GNU General Public License as published by fiore@5: the Free Software Foundation, either version 3 of the License, or fiore@5: (at your option) any later version. fiore@5: fiore@5: This program is distributed in the hope that it will be useful, fiore@5: but WITHOUT ANY WARRANTY; without even the implied warranty of fiore@5: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the fiore@5: GNU General Public License for more details. fiore@5: fiore@5: You should have received a copy of the GNU General Public License fiore@5: along with this program. If not, see . fiore@5: */ fiore@5: fiore@5: #include "uk_ac_qmul_eecs_ccmi_haptics_FalconHaptics.h" fiore@5: #include "HapticManager.h" fiore@5: #include fiore@5: #include fiore@5: #include fiore@5: #include fiore@5: #include fiore@5: #ifdef FREEGLUT fiore@5: #include fiore@5: #endif fiore@5: #include "utils.h" fiore@5: fiore@5: /* enum to match against when checking for the jump result */ fiore@5: enum {JMP_OK =0, JMP_EXIT }; fiore@5: fiore@5: /* Functions prototypes. */ fiore@5: /* callbacks */ fiore@5: void display(void); fiore@5: void reshape(int width, int height); fiore@5: void idle(void); fiore@5: void exitProcedure(void); fiore@5: void commandCallback(const jchar cmd, const jint ID, const jdouble x, const jdouble y,const jdouble startX, const jdouble startY); fiore@5: fiore@5: void initJniVariables(void); fiore@5: fiore@5: /* variables */ fiore@5: static HapticManager *hManager; fiore@5: static CollectionsManager *cManager; fiore@5: static JNIEnv *env; fiore@5: static jobject *lock; fiore@5: static jmp_buf jmpenv; fiore@5: static int jmpval = 0; fiore@5: static bool reshaped = false; fiore@5: /* jni variables */ fiore@5: static jclass hapticClass; fiore@5: static jclass hapticListenerClass; fiore@5: static jfieldID shutdownFieldId; fiore@5: static jfieldID hapticInitFailedfieldId; fiore@5: static jfieldID collectionsChangedFieldId; fiore@5: static jfieldID pickUpFieldId; fiore@5: static jfieldID attractToFieldId; fiore@5: /* hapticListener jni variables */ fiore@5: static jfieldID hapticListenerFieldId; fiore@5: static jfieldID cmdFieldId; // belongs to the haptic listener fiore@5: static jfieldID diagramElementFieldId; // belongs to the haptic listener fiore@5: static jfieldID xFieldId; // belongs to the haptic listener fiore@5: static jfieldID yFieldId; // belongs to the haptic listener fiore@5: static jfieldID startXFieldId; // belongs to the haptic listener fiore@5: static jfieldID startYFieldId; // belongs to the haptic listener fiore@5: static jobject hapticListener; fiore@5: /* synchronization methods ids */ fiore@5: static jmethodID notifyMethodId; fiore@5: static jmethodID notifyListenerMethodId; fiore@5: static jmethodID waitMethodId; fiore@5: fiore@5: /* Native initialization method for the Falcon Haptic device. Upon successful initialization a loop * fiore@5: * is started that communicates with the Java program in order to represent the diagram haptically */ fiore@5: JNIEXPORT jint JNICALL Java_uk_ac_qmul_eecs_ccmi_haptics_FalconHaptics_initFalcon fiore@5: (JNIEnv *environment, jobject obj, jint w, jint h){ fiore@5: fiore@5: env = environment; fiore@5: lock = &obj; fiore@5: /* fake main argv and argc as this is a dll */ fiore@5: char *argv[1] = {"FalconHaptics"}; fiore@5: int argc = 1; fiore@5: initJniVariables(); fiore@5: fiore@5: cManager = new CollectionsManager(env,lock); fiore@5: cManager->init(); fiore@5: hManager = new HapticManager(cManager,commandCallback); fiore@5: fiore@5: /* try to initialize the haptic, tell the java thread about the success/failure. The endeavour * fiore@5: * takes the lock on the haptics java object in order to access the nodes and edges concurrently */ fiore@5: if(env->MonitorEnter(*lock) != JNI_OK){ fiore@5: stopExecution("Could not allocate memory for haptic thread monitor"); fiore@5: } fiore@5: checkExceptions(env, "Could not enter monitor on Haptics"); fiore@5: fiore@5: bool mustSayGoodbye = false; fiore@5: fiore@5: if(!hManager->init()){ fiore@5: /* set the field in the java class to comunicate the main thread (waiting * fiore@5: * for the initialization to complete) that the initialization failed */ fiore@5: env->SetBooleanField(*lock,hapticInitFailedfieldId,JNI_TRUE); fiore@5: mustSayGoodbye = true; fiore@5: } fiore@5: fiore@5: if(mustSayGoodbye) fiore@5: std::cout << "Failed to initialize Falcon haptic device" << std::endl; fiore@5: else fiore@5: std::cout << "Falcon haptic device successfully initialized" << std::endl; fiore@5: fiore@5: /* notify the java thread */ fiore@5: env->CallVoidMethod(*lock,notifyMethodId); fiore@5: checkExceptions(env,"Could not call notify() on Haptics"); fiore@5: fiore@5: /* release the lock */ fiore@5: if(env->MonitorExit(*lock) != JNI_OK){ fiore@5: std::cerr << "Could not release memory for haptic thread monitor" << std::endl; fiore@5: exit(-1); fiore@5: } fiore@5: fiore@5: if(mustSayGoodbye) fiore@5: /* initialization failed: return */ fiore@5: return -1; fiore@5: fiore@5: /* haptic device is initialized, now build the openGL window */ fiore@5: fiore@5: /* glut initialization */ fiore@5: glutInit(&argc, argv); fiore@5: fiore@5: glutInitWindowSize( w, h ); fiore@5: glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH); fiore@5: glutCreateWindow( "Falcon Haptics Window" ); fiore@5: fiore@5: /* set up GLUT callback functions */ fiore@5: glutDisplayFunc ( display ); fiore@5: glutReshapeFunc ( reshape ); fiore@5: glutIdleFunc( idle ); fiore@5: fiore@5: /* use setjmp to be able to jump off the glutMainLoop when the user shuts the program down */ fiore@5: jmpval = setjmp(jmpenv); fiore@5: fiore@5: /* start the loop*/ fiore@5: if(jmpval == JMP_OK){ fiore@5: glutMainLoop(); fiore@5: } fiore@5: fiore@5: /* jmpval = JMP_EXIT, we're coming from the glut loop */ fiore@5: exitProcedure(); fiore@5: fiore@5: return 0; fiore@5: } fiore@5: fiore@5: /* GLUT display callback */ fiore@5: void display(){ fiore@5: /* takes the lock on the haptics java obejct in order to access the nodes and edges concurrently */ fiore@5: if(env->MonitorEnter(*lock) != JNI_OK){ fiore@5: stopExecution("Could not allocate memory for haptic thread monitor"); fiore@5: } fiore@5: checkExceptions(env,"Could not enter monitor on Haptics"); fiore@5: fiore@5: /* check if there is a shutdown request */ fiore@5: if( env->GetBooleanField(*lock,shutdownFieldId) == JNI_TRUE ){ fiore@5: // notify the other thread that this thread is about to die fiore@5: env->CallVoidMethod(*lock,notifyMethodId); fiore@5: checkExceptions(env, "Could not call notify() on Haptics"); fiore@5: // release the lock fiore@5: if(env->MonitorExit(*lock) != JNI_OK){ fiore@5: std::cerr << "Could not release memory for haptic thread monitor" << std::endl; fiore@5: } fiore@5: longjmp(jmpenv,JMP_EXIT); fiore@5: } fiore@5: fiore@5: /* check if the node and edges collections have changed since the last frame or if * fiore@5: * the window has been reshaped. In either case the haptic scene must be rendered again */ fiore@5: bool collectionsChanged = false; fiore@5: if(env->GetBooleanField(*lock,collectionsChangedFieldId) == JNI_TRUE || reshaped){ fiore@5: if(reshaped) fiore@5: reshaped = false; fiore@5: /* remember that collections have changed (will be used later in hManager->drawDiagram) */ fiore@5: collectionsChanged = true; fiore@5: /* set the Java boolean variable back to false */ fiore@5: env->SetBooleanField(*lock,collectionsChangedFieldId,JNI_FALSE); fiore@5: } fiore@5: fiore@5: /* check if the user picked up an object */ fiore@5: jboolean picked_up = env->GetBooleanField(*lock,pickUpFieldId); fiore@5: if(picked_up == JNI_TRUE){ fiore@5: env->SetBooleanField(*lock,pickUpFieldId,JNI_FALSE); fiore@5: } fiore@5: fiore@5: /* check if the user asked to be attracted to a node */ fiore@5: jint attract_to = env->GetIntField(*lock,attractToFieldId); fiore@5: if(attract_to != HapticManager::NO_ID){ fiore@5: env->SetIntField(*lock,attractToFieldId,HapticManager::NO_ID); fiore@5: } fiore@5: fiore@5: /* --- start drawing --- */ fiore@5: /* Set up OpenGL state. */ fiore@5: glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); fiore@5: glEnable(GL_DEPTH_TEST); fiore@5: glDepthFunc(GL_LESS); fiore@5: glDepthMask(GL_TRUE); fiore@5: glEnable(GL_CULL_FACE); fiore@5: glShadeModel(GL_SMOOTH); fiore@5: glEnable( GL_LIGHTING ); fiore@5: glEnable(GL_LIGHT0); fiore@5: glLoadIdentity(); fiore@5: fiore@5: gluLookAt(0, 0, 0.1, // eye position .05 fiore@5: 0, 0, 0, // center position fiore@5: 0, 1, 0); // which direction is up fiore@5: glEnable( GL_NORMALIZE ); fiore@5: fiore@5: /* draw the diagram graphicly and, if the collection has changed, haptically too */ fiore@5: hManager->drawCursor(); fiore@5: hManager->drawDiagram(collectionsChanged,(picked_up == JNI_TRUE),attract_to); fiore@5: fiore@5: fiore@5: /* release lock. HapticManager methods from now on can execute commands without deadlock risk */ fiore@5: if(env->MonitorExit(*lock) != JNI_OK){ fiore@5: stopExecution("Could not release memory for haptic thread monitor"); fiore@5: } fiore@5: /* check button status */ fiore@5: hManager->checkButtons(); fiore@5: fiore@5: /* check if un unselect command is to be issued, for an object being touched has been deleted */ fiore@5: hManager->checkUnselection(); fiore@5: fiore@5: /* check collisions. priority to nodes */ fiore@5: bool node_collision = hManager->checkNodeCollision(); fiore@5: if(!node_collision) // only one object at time can be touched fiore@5: hManager->checkEdgeCollision(); fiore@5: /* check motion feedback */ fiore@5: hManager->checkMotion(); fiore@5: fiore@5: /* swap graphic buffers. */ fiore@5: glutSwapBuffers(); fiore@5: } fiore@5: fiore@5: /* GLUT reshape callback */ fiore@5: void reshape (int w, int h){ fiore@5: cManager->setScreenHeight(h); fiore@5: cManager->setScreenWidth(w); fiore@5: reshaped = true; fiore@5: fiore@5: static const double ANGLE = 100; fiore@5: glViewport(0, 0, w, h); fiore@5: glMatrixMode(GL_PROJECTION); fiore@5: glLoadIdentity(); fiore@5: gluPerspective(ANGLE, // angle fiore@5: (float)w / h, // aspect ratio fiore@5: 0.01, // near clipping plane .01 fiore@5: 500);// far clipping plane 1000 fiore@5: glMatrixMode(GL_MODELVIEW); fiore@5: } fiore@5: fiore@5: /* GLUT idle callback */ fiore@5: void idle(){ fiore@5: glutPostRedisplay(); fiore@5: } fiore@5: fiore@5: /* called before exit */ fiore@5: void exitProcedure(){ fiore@5: hManager->dispose(); fiore@5: delete hManager; fiore@5: delete cManager; fiore@5: } fiore@5: fiore@5: /* initialize all the variables needed for the jni access to the Falcon Haptics class by the Haptic thread*/ fiore@5: void initJniVariables(void){ fiore@5: /* --- CLASSES --- */ fiore@5: //this class fiore@5: hapticClass = env->GetObjectClass(*lock); fiore@5: if(hapticClass == NULL){ fiore@5: stopExecution("Could not find the Haptics class"); fiore@5: } fiore@5: // the haptic listener, member of this class fiore@5: hapticListenerClass = env->FindClass("Luk/ac/qmul/eecs/ccmi/haptics/HapticListenerThread;"); fiore@5: if(hapticListenerClass == NULL){ fiore@5: stopExecution("Could not find the haptic listener class"); fiore@5: } fiore@5: fiore@5: /* --- FIELD IDS --- */ fiore@5: fiore@5: // boolean set to this thread to notify the java thread the unsuccessful initialization of haptic device fiore@5: hapticInitFailedfieldId = env->GetFieldID(hapticClass,"initFailed", "Z"); fiore@5: if(hapticInitFailedfieldId == NULL){ fiore@5: stopExecution("failed to find the initFailed field id"); fiore@5: } fiore@5: fiore@5: // boolean set by the java thread to notify this thread the program has been shut down fiore@5: shutdownFieldId = env->GetFieldID(hapticClass, "shutdown", "Z"); fiore@5: if(shutdownFieldId == NULL){ fiore@5: stopExecution("failed to find the shutdownfieldId field id"); fiore@5: } fiore@5: fiore@5: collectionsChangedFieldId = env->GetFieldID(hapticClass, "collectionsChanged", "Z"); fiore@5: if(collectionsChangedFieldId == NULL){ fiore@5: stopExecution("failed to find the collectionsChangedFieldId field id"); fiore@5: } fiore@5: fiore@5: attractToFieldId = env->GetFieldID(hapticClass, "attractTo" , "I"); fiore@5: if(attractToFieldId == NULL){ fiore@5: stopExecution("failed to find the attractTo field id"); fiore@5: } fiore@5: fiore@5: pickUpFieldId = env->GetFieldID(hapticClass,"pickUp","Z"); fiore@5: if(pickUpFieldId == NULL){ fiore@5: stopExecution("failed to find the pickUp field id"); fiore@5: } fiore@5: fiore@5: hapticListenerFieldId = env->GetFieldID(hapticClass, "hapticListener", "Luk/ac/qmul/eecs/ccmi/haptics/HapticListenerThread;"); fiore@5: if(hapticListenerFieldId == NULL){ fiore@5: stopExecution("failed to find the hapticListenerThread field id"); fiore@5: } fiore@5: fiore@5: cmdFieldId = env->GetFieldID(hapticListenerClass,"cmd", "C"); fiore@5: if(cmdFieldId == NULL){ fiore@5: stopExecution("failed to find the cmd field id of the hapticListener class"); fiore@5: } fiore@5: fiore@5: diagramElementFieldId = env->GetFieldID(hapticListenerClass, "diagramElementID", "I"); fiore@5: if(diagramElementFieldId == NULL){ fiore@5: stopExecution("failed to find the diagramElement field id of the hapticListener class"); fiore@5: } fiore@5: fiore@5: xFieldId = env->GetFieldID(hapticListenerClass, "x", "D"); fiore@5: if(xFieldId == NULL){ fiore@5: stopExecution("failed to find the x field id of the hapticListener class"); fiore@5: } fiore@5: fiore@5: yFieldId = env->GetFieldID(hapticListenerClass, "y", "D"); fiore@5: if(yFieldId == NULL){ fiore@5: stopExecution("failed to find the y field id of the hapticListener class"); fiore@5: } fiore@5: fiore@5: startXFieldId = env->GetFieldID(hapticListenerClass, "startX", "D"); fiore@5: if(startXFieldId == NULL){ fiore@5: stopExecution("failed to find the x field id of the hapticListener class"); fiore@5: } fiore@5: fiore@5: startYFieldId = env->GetFieldID(hapticListenerClass, "startY", "D"); fiore@5: if(startYFieldId == NULL){ fiore@5: stopExecution("failed to find the y field id of the hapticListener class"); fiore@5: } fiore@5: fiore@5: hapticListener = env->GetObjectField(*lock,hapticListenerFieldId); fiore@5: /* --- SYNCHRONIZATION METHODS ID --- */ fiore@5: // notify() fiore@5: notifyMethodId = env->GetMethodID(hapticClass,"notify","()V"); fiore@5: if(notifyMethodId == NULL){ fiore@5: stopExecution("failed to find the notify method id"); fiore@5: } fiore@5: fiore@5: notifyListenerMethodId = env->GetMethodID(hapticListenerClass,"notify","()V"); fiore@5: if(notifyListenerMethodId == NULL){ fiore@5: stopExecution("failed to find the notify method id"); fiore@5: } fiore@5: // wait() fiore@5: waitMethodId = env->GetMethodID(hapticListenerClass,"wait","()V"); fiore@5: if(waitMethodId == NULL){ fiore@5: stopExecution("failed to find the wait method id"); fiore@5: } fiore@5: } fiore@5: fiore@5: void commandCallback(const jchar cmd, const jint ID, const jdouble x, const jdouble y, const jdouble startX, const jdouble startY){ fiore@5: /* the haptic listener java thread is waiting for commands fiore@5: first set the variable the Haptic Listener java thread will read after being notified, fiore@5: then notify and get it awake. Thus wait for the java thread to notify that the command fiore@5: has been accomplished. This is done as otherwise some commands might be neglected. as if the thread fiore@5: scheduler decides to execute twice this routine without executing the java thread in the middle then fiore@5: the former command gets overwritten by the latter. fiore@5: Note the monitor is hapticListener and not haptics as for the draw function. fiore@5: When in this routine, this thread does not hold the lock on haptics as if a command results in changing fiore@5: the elements collections (e.g. moveNode) all those methods are synchronized and require to acquire the lock on haptics. fiore@5: Since this thread would wait for the command to be executed by the java thread, which in turns would wait for this fiore@5: thread to release the lock on haptics, that would result in a deadlock. fiore@5: */ fiore@5: if(env->MonitorEnter(hapticListener) != JNI_OK){ fiore@5: stopExecution("Could not allocate memory for haptic listener thread monitor"); fiore@5: } fiore@5: checkExceptions(env,"Could not enter monitor on the haptic listener"); fiore@5: /* Fill all the shared variables for informaton for the command to be executed */ fiore@5: env->SetCharField(hapticListener,cmdFieldId,cmd); fiore@5: env->SetIntField(hapticListener,diagramElementFieldId,ID); fiore@5: env->SetDoubleField(hapticListener,xFieldId,x); fiore@5: env->SetDoubleField(hapticListener,yFieldId,y); fiore@5: env->SetDoubleField(hapticListener,startXFieldId,startX); fiore@5: env->SetDoubleField(hapticListener,startYFieldId,startY); fiore@5: // wake the java thread up to execute the command fiore@5: env->CallVoidMethod(hapticListener,notifyListenerMethodId); fiore@5: checkExceptions(env, "Could not call notify() on HapticListener"); fiore@5: /* wait for the commands to be executed. Here is actually where the monitor is * fiore@5: * freed and the java thread starts to execute the command, having been notified */ fiore@5: env->CallVoidMethod(hapticListener,waitMethodId); fiore@5: checkExceptions(env, "Could not call wait() on HapticListener"); fiore@5: if(env->MonitorExit(hapticListener) != JNI_OK){ fiore@5: stopExecution("Could not release memory for haptic listener thread monitor"); fiore@5: } fiore@5: }