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: }