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