view native/Falcon/uk_ac_qmul_eecs_ccmi_haptics_FalconHaptics.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
/*  
 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");
	}
}