Mercurial > hg > ccmieditor
view native/PhantomOmni/uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics.cpp @ 8:ea7885bd9bff tip
fixed bug : render solid line as dotted/dashed when moving the stylus from dotted/dashed to solid
author | ccmi-guest |
---|---|
date | Thu, 03 Jul 2014 16:12:20 +0100 |
parents | d66dd5880081 |
children |
line wrap: on
line source
#include "uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics.h" #include "stdafx.h" #include "GraphicManager.h" #include "HapticManager.h" #include "HapticException.h" #include <stdlib.h> #include <math.h> #include <assert.h> #include <setjmp.h> #include "utils.h" #define CURSOR_SIZE_PIXELS 20 enum {JMP_OK =0, JMP_EXIT }; /************************** Function prototypes. ***************************/ /* callbacks */ void displayCallback(void); void reshapeCallback(int width, int height); void idleCallback(void); void exitProcedure(void); void hapticCommandCB(const jchar cmd, const jint ID, const jdouble x, const jdouble y,const jdouble startX, const jdouble startY); void drawCursor(); void updateWorkspace(); void initJniVariables(void); /************************* global variables **************************/ static GraphicManager *gManager; static HapticManager *hManager; static CollectionsManager *cManager; JNIEnv *env; jobject *lock; static int width; static int height; static jmp_buf jmpenv; static int jmpval = 0; /* jni variables */ jclass hapticClass; jclass hapticListenerClass; jfieldID shutdownfieldId; jfieldID newHapticIdfieldId; jfieldID dumpHapticIdfieldId; jfieldID currentHapticIdfieldId; jfieldID hapticInitFailedfieldId; jfieldID attractTofieldId; jfieldID attractToHapticIdFieldId; jfieldID pickUpfieldId; jfieldID pickUpHapticIdFieldId; jfieldID hapticListenerFieldId; jfieldID cmdFieldId; // belongs to the haptic listener jfieldID diagramElementFieldId; // belongs to the haptic listener jfieldID xFieldId; // belongs to the haptic listener jfieldID yFieldId; // belongs to the haptic listener jfieldID startXFieldId; // belongs to the haptic listener jfieldID startYFieldId; // belongs to the haptic listener jobject hapticListener; jmethodID notifyMethodId; jmethodID notifyListenerMethodId; jmethodID waitMethodId; /******************************************************************************* Initializes GLUT for displaying a simple haptic scene. *******************************************************************************/ JNIEXPORT jint JNICALL Java_uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_initOmni (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] = {"OmniHaptics"}; int argc = 1; initJniVariables(); /* glut initialization */ glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize(w, h); glutCreateWindow("CCmI Diagram Haptics"); /* glut callbacks */ glutDisplayFunc(displayCallback); glutReshapeFunc(reshapeCallback); glutIdleFunc(idleCallback); cManager = new CollectionsManager(env,lock); hManager = new HapticManager(cManager,hapticCommandCB); gManager = new GraphicManager(cManager,hManager); cManager->init(); gManager->init(); // 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; try{ hManager->init(); }catch (HapticException e){ /* 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 Omni Haptic device" << std::endl; else std::cout << "Omni Haptic device successfully initialized" << std::endl; // notify the other 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; /* use setjmp to be able to jump off the glutMainLoop when the user shuts the program down */ jmpval = setjmp(jmpenv); /* star the loop*/ if(jmpval == JMP_OK){ glutMainLoop(); } exitProcedure(); return 0; } /******************************************************************************* GLUT callback for redrawing the view. *******************************************************************************/ void displayCallback(){ // 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 user asked to be attracted to a node jboolean attractTo = env->GetBooleanField(*lock,attractTofieldId); jint attractToDiagramId = 0; if(attractTo == JNI_TRUE){ env->SetBooleanField(*lock,attractTofieldId,JNI_FALSE); jint attractToHapticId = env->GetIntField(*lock,attractToHapticIdFieldId); if(cManager->isNode(attractToHapticId)){ attractToDiagramId = cManager->getNodeDataFromID(attractToHapticId).diagramId; }else{ attractToDiagramId = cManager->getEdgeDataFromID(attractToHapticId).diagramId; } hManager->setAttractTo(attractToHapticId); } // check if the user picked up a node jboolean pickUp = env->GetBooleanField(*lock,pickUpfieldId); jint pickUpDiagramId = 0; if(pickUp == JNI_TRUE){ env->SetBooleanField(*lock,pickUpfieldId,JNI_FALSE); jint pickUpHapticId = env->GetIntField(*lock,pickUpHapticIdFieldId); if(cManager->isNode(pickUpHapticId)){ pickUpDiagramId = cManager->getNodeDataFromID(pickUpHapticId).diagramId; }else{ pickUpDiagramId = cManager->getEdgeDataFromID(pickUpHapticId).diagramId; } hManager->pickUp(pickUpDiagramId); } // draw the scene graphically and haptically hManager->draw(); gManager->draw(); // check whether the java thread needs to either create or dump an haptic id jboolean needsNewHapticId = env->GetBooleanField(*lock,newHapticIdfieldId); if(needsNewHapticId == JNI_TRUE){ // set int currentHapticId of class Haptics with a new generated id jint newHapticid = hlGenShapes(1); env->SetIntField(*lock, currentHapticIdfieldId, newHapticid); //set the boolean field to false as the other thread now has an id env->SetBooleanField(*lock, newHapticIdfieldId, JNI_FALSE); //notify the other thread env->CallVoidMethod(*lock,notifyMethodId); checkExceptions(env, "Could not call notify() on Haptics"); } jboolean needsDumpOldHapticId = env->GetBooleanField(*lock, dumpHapticIdfieldId); if(needsDumpOldHapticId == JNI_TRUE){ // get the id of the deleted element from the other thread jint oldHapticId = env->GetIntField(*lock,currentHapticIdfieldId); // free the old haptic id hlDeleteShapes(oldHapticId,1); // set the boolean field as the id has been cleaned up env->SetBooleanField(*lock,dumpHapticIdfieldId,JNI_FALSE); // notify the other thread env->CallVoidMethod(*lock,notifyMethodId); checkExceptions(env, "Could not call notify() on Haptics"); } /* release lock */ if(env->MonitorExit(*lock) != JNI_OK){ stopExecution("Could not release memory for haptic thread monitor"); } /* it's important that this call be outside the monitors, else a deadlock occurs */ // Call any event callbacks that have been triggered. if(attractTo == JNI_TRUE){ if(hManager->wasAlreadyTouchingAttractingElement()){ hapticCommandCB('t',attractToDiagramId,0,0,0,0); } } hlCheckEvents(); /* button one is not based on events only , but also on time lapse as the single click is effective * * after an amount of time (if another click is not issued). Therefore we need to continuosly for the * * button status, hence the work must be done here and not in the click callbacks */ HapticManager::ClickStatus click = hManager->getButton1Status(); if(click == HapticManager::ONE_CLICK){ hManager->doButton1Click(false); }else if(click == HapticManager::TWO_CLICK){ hManager->doButton1Click(true); } glutSwapBuffers(); } /******************************************************************************* GLUT callback for reshaping the window. This is the main place where the viewing and workspace transforms get initialized. *******************************************************************************/ void reshapeCallback(int w, int h){ static const double kPI = 3.1415926535897932384626433832795; static const double kFovY = 40; static const double nearDist = 1.0 / tan((kFovY / 2.0) * kPI / 180.0 /* radiants for 1 degree */); static const double farDist = nearDist + 2.0; double aspect; width = w; height = h; cManager->setScreenHeight(h); glViewport(0, 0, width, height); // Compute the viewing parameters based on a fixed fov and viewing // a canonical box centered at the origin. aspect = (double) width/height ; glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(kFovY, aspect,nearDist, farDist); // Place the camera down the Z axis looking at the origin. glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(0, 0, nearDist + 1.0, 0, 0, 0, 0, 1, 0); hduVector3Dd origin; fromScreen(hduVector3Dd(0, 0, 0),origin); glLoadIdentity(); gluLookAt(-origin[0],origin[1], nearDist + 1.0, -origin[0],origin[1], 0, 0, 1, 0); hduVector3Dd end; fromScreen(hduVector3Dd(w, h, 0),end); //glTranslatef(end[0]-origin[0],-(end[1]-origin[1]),0); updateWorkspace(); } /******************************************************************************* Use the current OpenGL viewing transforms to initialize a transform for the haptic device workspace so that it's properly mapped to world coordinates. *******************************************************************************/ void updateWorkspace(){ GLdouble modelview[16]; GLdouble projection[16]; GLint viewport[4]; glGetDoublev(GL_MODELVIEW_MATRIX, modelview); glGetDoublev(GL_PROJECTION_MATRIX, projection); glGetIntegerv(GL_VIEWPORT, viewport); hlMatrixMode(HL_TOUCHWORKSPACE); hlLoadIdentity(); // Fit haptic workspace to view volume. hluFitWorkspace(projection); // Compute cursor scale. gManager->gCursorScale = hluScreenToModelScale(modelview, projection, viewport); gManager->gCursorScale *= CURSOR_SIZE_PIXELS; hduVector3Dd p0, p1; bool bNoError; bNoError = fromScreen(hduVector3Dd(0, 0, 0), p0); assert(bNoError); bNoError = fromScreen(hduVector3Dd(1, 1, 0), p1); assert(bNoError); double m_windowTworldScale = (p1 - p0).magnitude() / sqrt(2.0); gManager->gWorldScale = m_windowTworldScale; } /******************************************************************************* GLUT callback for idle state. Use this as an opportunity to request a redraw. Checks for HLAPI errors that have occurred since the last idle check. *******************************************************************************/ void idleCallback(){ HLerror error; while (HL_ERROR(error = hlGetError())){ std::cerr << "HL Error: " << error.errorCode << std::endl <<error.errorInfo << std::endl; if (error.errorCode == HL_DEVICE_ERROR){ hduPrintError(stderr, &error.errorInfo, "Device error\n"); std::cout << "sending error message to haptic listener" << std::endl; hapticCommandCB('e',0,0,0,0,0); } } glutPostRedisplay(); } /******************************************************************************* This handler is called when the application is exiting. Deallocates any state and cleans up. *******************************************************************************/ void exitProcedure(){ // Free up the haptic rendering context. hlMakeCurrent(NULL); if (HapticManager::ghHLRC != NULL){ hlDeleteContext(HapticManager::ghHLRC); } // Free up the haptic device. if (HapticManager::ghHD != HD_INVALID_HANDLE){ hdDisableDevice(HapticManager::ghHD); } std::cout << "freeing haptic resources" << std::endl; } /* initialize all the variable needed for the jni access to the Haptics class from the openGL 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 by the java thread when an element is added and it needs a new id from the haptic library newHapticIdfieldId = env->GetFieldID(hapticClass,"newHapticId", "Z"); if(newHapticIdfieldId == NULL){ stopExecution("failed to find the newHapticId field id"); } // boolean set by the java thread when an element is added and it needs a new id from the haptic library dumpHapticIdfieldId = env->GetFieldID(hapticClass,"dumpHapticId", "Z"); if(dumpHapticIdfieldId == NULL){ stopExecution("failed to find the dumpHapticId field id"); } // boolean set to this thread to notify the java thread the unsuccessful initialization of haptic device hapticInitFailedfieldId = env->GetFieldID(hapticClass,"hapticInitFailed", "Z"); if(hapticInitFailedfieldId == NULL){ stopExecution("failed to find the hapticInitFailedfieldId 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"); } // boolean set by the java thread when the user asks to sna attractTofieldId = env->GetFieldID(hapticClass, "attractTo" , "Z"); if(attractTofieldId == NULL){ stopExecution("failed to find the attractTo field id"); } attractToHapticIdFieldId = env->GetFieldID(hapticClass, "attractToHapticId", "I"); if(attractToHapticIdFieldId == NULL){ stopExecution("failed to find the attractToHapticId field id"); } pickUpfieldId = env->GetFieldID(hapticClass,"pickUp","Z"); if(pickUpfieldId == NULL){ stopExecution("failed to find the pickUp field id"); } pickUpHapticIdFieldId = env->GetFieldID(hapticClass,"pickUpHapticId","I"); if(pickUpHapticIdFieldId == NULL){ stopExecution("failed to find pickUpHapticId 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"); } // variable to exchange values between threads currentHapticIdfieldId = env->GetFieldID(hapticClass,"currentHapticId","I"); if(currentHapticIdfieldId == NULL){ stopExecution("failed to find the currentHapticId field"); } 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); /* --- METHOD IDs --- */ // notify method 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"); } waitMethodId = env->GetMethodID(hapticListenerClass,"wait","()V"); if(waitMethodId == NULL){ stopExecution("failed to find the wait method id"); } } void hapticCommandCB(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. */ /* now wake up the haptic listener */ 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"); 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"); } }