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");
+	}
+}