view native/PhantomOmni/HapticManager.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 9e67171477bc
children ea7885bd9bff
line wrap: on
line source
#include "HapticManager.h"

#define NO_ERROR_SIMUL


void checkExceptions(void);
void stopExecution(char* msg);

bool movedNode = false;


void HLCALLBACK HapticManager::hlButton1CB(HLenum event, HLuint object,
                                         HLenum thread, HLcache *cache, 
										 void *userdata)
{
	HapticManager *pThis = static_cast<HapticManager*>(userdata);
	if(object == pThis->wall1Id||object == pThis->wall2Id) // no event is associated to the wall 
		return;
	if(event != HL_EVENT_1BUTTONDOWN)
		return;

	/* check if it was a double click or not */
	
	clock_t time = clock();
	if(pThis->clickStart == 0) // first click
		pThis->clickStart = time; 
	else if(time < (pThis->clickStart + CLICK_INTERVAL))
		pThis->doubleClick = true;
	
}

void HLCALLBACK HapticManager::hlButton2CB(HLenum event, HLuint object,
                                         HLenum thread, HLcache *cache, 
										 void *userdata){
	HapticManager *pThis = static_cast<HapticManager*>(userdata);
	if(event == HL_EVENT_2BUTTONDOWN){
		pThis->stickyMode = !pThis->stickyMode;
		pThis->executeCommand('g', pThis->stickyMode ? 1 : 0,0,0,0,0);
		/* we change mode to stickyMode, thus dragging becomes not active */
		if(pThis->stickyMode && pThis->springStatus == DRAGGING){
				pThis->springStatus = STOP_DRAGGING;
				pThis->secondClick = false;
		}
#ifdef VIBRATION
		// vibrate 
		if(pThis->vibrationStatus == NO_VIBRATION){
			pThis->vibrationStatus = START_VIBRATION;
		}
#endif
	}
}

void HLCALLBACK HapticManager::hlTouchCB(HLenum event, HLuint object, HLenum thread,
										 HLcache *cache, void *userdata)
{
	HapticManager *pThis = static_cast<HapticManager*>(userdata);
	if(object == pThis->wall1Id||object == pThis->wall2Id) // no event is associated to the wall 
		return;
	if(pThis->lastMovedElement == object){
		pThis->stillTouchingLastMovedElement = true;
	}
	
	try{
		// check if the touched object is an edge
		if(pThis->collectionsManager->isEdge(object)){
			CollectionsManager::EdgeData & ed = pThis->collectionsManager->getEdgeDataFromID(object);
			pThis->lastTouchedEdgeID = object;
			pThis->lastTouchedEdgeIsUntouched = false;
			pThis->lastTouchedEdgeStipplePattern = ed.stipplePattern;
			// check if the proxy is still touching the edge's node, in that case only play the sound
			HLboolean isTouching = HL_FALSE;
			if(pThis->lastTouchedNodeID != HL_OBJECT_ANY){
				hlGetShapeBooleanv(pThis->lastTouchedNodeID,HL_PROXY_IS_TOUCHING,&isTouching);
			}
			/* play the sound and speech associated to the edge */
			if((isTouching == HL_TRUE||pThis->stillTouchingLastMovedElement) && (object != pThis->attractToHapticId)){
				if(!pThis->stickyMode)
					pThis->executeCommand('p',ed.diagramId,0,0,0,0); // just plays the sound
			}else{
				pThis->executeCommand('s',ed.diagramId,0,0,0,0);
				pThis->lastHighlightElementID = object;
				pThis->executeCommand('t',ed.diagramId,0,0,0,0); // speaks the edge's name
			}
		}else{ // it's a node 
			pThis->lastTouchedNodeID = object;
			CollectionsManager::NodeData & nd = pThis->collectionsManager->getNodeDataFromID(object);
			pThis->lastTouchedNodePosition[0] = nd.x;
			pThis->lastTouchedNodePosition[1] = nd.y;
			pThis->lastTouchedNodePosition[2] = 0;
			pThis->frictionDisabled = true;
			pThis->executeCommand('s',nd.diagramId,0,0,0,0);
			pThis->lastHighlightElementID = object;
			pThis->executeCommand('t',nd.diagramId,0,0,0,0);
		}	
	}catch(int){
		stopExecution("Touch Call Back: Could not retrieve element from ID");
	}
}

void HLCALLBACK HapticManager::hlUntouchCB(HLenum event, HLuint object, HLenum thread,
										   HLcache *cache, void *userdata)
{
	HapticManager *pThis = static_cast<HapticManager*>(userdata);
	if(object == pThis->wall1Id||object == pThis->wall2Id) // no event is associated to the wall 
		return;
	
	if(object == pThis->lastMovedElement){
		pThis->lastMovedElement = HL_OBJECT_ANY;
		pThis->stillTouchingLastMovedElement = false;
	}
	if(pThis->lastHighlightElementID == object){
		pThis->lastHighlightElementID = HL_OBJECT_ANY;
		pThis->executeCommand('u',0,0,0,0,0);
	}
	if(pThis->lastTouchedEdgeID == object){
		pThis->lastTouchedEdgeIsUntouched = true;
	}
}


void HLCALLBACK HapticManager::hlMotionCB(HLenum event, HLuint object, HLenum thread,
										   HLcache *cache, void *userdata)
{
	HapticManager *pThis = static_cast<HapticManager*>(userdata);
	
	/* plays the sound to acknowledge the user they're dragging an element around */
	static int onceinawhile = 0;
	if(pThis->springStatus == DRAGGING){
		onceinawhile = (onceinawhile+1)%5;
		if(!onceinawhile)
			pThis->executeCommand('g',3,0,0,0,0);
	}
	
	/* the following code is to play element speech when taking a path through an edge starting from a connected node */
	
	/* friction is disabled when we land on a node. If proxy ain't on a node, then just return  */
	/* else it would play every time we move along the edge even if not detouching from a node  */
	if(!pThis->frictionDisabled){
		return;
	}

	/* calculate the distance between the node, the stylus was on, and the current proxy position after moving out from it*/
	HLdouble proxyPosition[3];
	hlGetDoublev(HL_DEVICE_POSITION,proxyPosition);
	proxyPosition[0] -= pThis->lastTouchedNodePosition[0];
	proxyPosition[1] -= pThis->lastTouchedNodePosition[1];
	proxyPosition[2] -= pThis->lastTouchedNodePosition[2];
	if(pThis->lastTouchedEdgeID != HL_OBJECT_ANY){
		if(norm(proxyPosition) > EDGE_SPEECH_DISTANCE_WHEN_LEAVING_NODE){
			pThis->frictionDisabled = false;
			HLboolean isTouching;
			hlGetShapeBooleanv(pThis->lastTouchedEdgeID,HL_PROXY_IS_TOUCHING,&isTouching);
			if(isTouching == HL_TRUE){
				try{
					CollectionsManager::EdgeData & ed = pThis->collectionsManager->getEdgeDataFromID(pThis->lastTouchedEdgeID);
					pThis->executeCommand('t',ed.diagramId,0,0,0,0);
					pThis->lastTouchedEdgeID = ed.hapticId;
					pThis->lastHighlightElementID = ed.hapticId;
					pThis->executeCommand('s',ed.diagramId,0,0,0,0);
				}catch(int){
					stopExecution("Button 1 callback: Could not retrieve edge from ID");
				}
			}
		}
	}
}

#ifdef VIBRATION
void HLCALLBACK HapticManager::vibrationCB(HDdouble force[3], HLcache *cache, void *userdata){
	static const hduVector3Dd direction( 0, 1, 0 );
    HDdouble instRate;
    static HDdouble timer = 0;

	HapticManager *pThis = static_cast<HapticManager*>(userdata);

    /* Use the reciprocal of the instantaneous rate as a timer. */
    hdGetDoublev(HD_INSTANTANEOUS_UPDATE_RATE, &instRate);
    timer += 1.0 / instRate;

    /* Apply a sinusoidal force in the direction of motion. */
    hduVecScale(force, direction, VIBRATION_AMPLITUDE * sin(timer * VIBRATION_FREQUENCY));
    hdSetDoublev(HD_CURRENT_FORCE, force);
}

void HLCALLBACK HapticManager::beforeVibrationCB(HLcache *cache, void *userdata){}

void HLCALLBACK HapticManager::afterVibrationCB(HLcache *cache, void *userdata){}
#endif

void HapticManager::init(void) throw(HapticException){
	HDErrorInfo error;

    ghHD = hdInitDevice(HD_DEFAULT_DEVICE);
    if (HD_DEVICE_ERROR(error = hdGetError())){
        throw HapticException();
    }
    
    ghHLRC = hlCreateContext(ghHD);
    hlMakeCurrent(ghHLRC);

    // Enable optimization of the viewing parameters when rendering geometry for OpenHaptics.
    hlEnable(HL_HAPTIC_CAMERA_VIEW);

    hlTouchableFace(HL_FRONT);
	wall1Id = hlGenShapes(1);
	wall2Id = hlGenShapes(1);
	attractToHapticId = HL_OBJECT_ANY;
	lastTouchedNodeID = HL_OBJECT_ANY;
	lastTouchedEdgeID = HL_OBJECT_ANY;
	draggedElementID = HL_OBJECT_ANY;
	lastHighlightElementID = HL_OBJECT_ANY;

	// initialize effects
	frictionFX = hlGenEffects(1);
	vibrationFX = hlGenEffects(1);
	attractionFX = hlGenEffects(1);
	springFX = hlGenEffects(1);
	displayList1 = 0;
	displayList2 = 0;
	hlEventd(HL_EVENT_MOTION_LINEAR_TOLERANCE,5);
	hlEventd(HL_EVENT_MOTION_ANGULAR_TOLERANCE,5);

	// add callbacks 
	hlAddEventCallback(HL_EVENT_2BUTTONDOWN,HL_OBJECT_ANY,HL_CLIENT_THREAD,hlButton2CB,this);
	hlAddEventCallback(HL_EVENT_2BUTTONUP,HL_OBJECT_ANY,HL_CLIENT_THREAD,hlButton2CB,this);
	hlAddEventCallback(HL_EVENT_1BUTTONUP,HL_OBJECT_ANY,HL_CLIENT_THREAD,hlButton1CB,this);
	hlAddEventCallback(HL_EVENT_1BUTTONDOWN,HL_OBJECT_ANY,HL_CLIENT_THREAD,hlButton1CB,this);
	hlAddEventCallback(HL_EVENT_TOUCH, HL_OBJECT_ANY, HL_CLIENT_THREAD, hlTouchCB, this);
	hlAddEventCallback(HL_EVENT_UNTOUCH, HL_OBJECT_ANY, HL_CLIENT_THREAD, hlUntouchCB, this);
	hlAddEventCallback(HL_EVENT_MOTION, HL_OBJECT_ANY, HL_CLIENT_THREAD, hlMotionCB, this);

#ifdef VIBRATION
	hlBeginFrame();
	hlCallback(HL_EFFECT_START,(HLcallbackProc) beforeVibrationCB, this);
	hlCallback(HL_EFFECT_STOP,(HLcallbackProc) afterVibrationCB, this);
    hlCallback(HL_EFFECT_COMPUTE_FORCE, (HLcallbackProc) vibrationCB, this);
	hlEndFrame();
#endif
}

HapticManager::ClickStatus HapticManager::getButton1Status(){
	if(clickStart == 0){
		return NO_CLICK;
	}else if(doubleClick){
		/* double click: set everything back to default value and return DOUBLE_CLICK */
		clickStart = 0;
		doubleClick = false;
		return TWO_CLICK;
	}else if(clock() >= clickStart + CLICK_INTERVAL || secondClick) {
		/* one click and enough time elapsed, so that it cannot be a double click */
		clickStart = 0;
		return ONE_CLICK;
	}
	return NO_CLICK;
}

void HapticManager::doButton1Click(bool doubleClick){
	/* DOUBLE CLICK*/
	if(doubleClick){
		if(lastTouchedNodeID != HL_OBJECT_ANY){
			HLboolean isTouching = HL_FALSE;
			hlGetShapeBooleanv(lastTouchedNodeID,HL_PROXY_IS_TOUCHING,&isTouching); // priority to nodes
			if(isTouching == HL_TRUE||lastTouchedNodeID == lastMovedElement){
				try{
					CollectionsManager::NodeData & nd = collectionsManager->getNodeDataFromID(lastTouchedNodeID);
					executeCommand('i',nd.diagramId, 0,0,0,0);
				}catch(int){
					stopExecution("Button 1 callback: Could not retrieve node from ID");
				}
			}else{
				if(lastTouchedEdgeID != HL_OBJECT_ANY){
					hlGetShapeBooleanv(lastTouchedEdgeID,HL_PROXY_IS_TOUCHING,&isTouching);
					if(isTouching == HL_TRUE||lastTouchedNodeID == lastMovedElement){
						try{
							CollectionsManager::EdgeData & ed = collectionsManager->getEdgeDataFromID(lastTouchedEdgeID);
							executeCommand('i',ed.diagramId, 0,0,0,0);
						}catch(int){
							stopExecution("Button 1 callback: Could not retrieve edge from ID");
						}
					}
				}
			}
		}
		// this was a double click, thus clean up the data about the dragging
		if(springStatus == DRAGGING){
			springStatus = STOP_DRAGGING;
			secondClick = false;
		}
		return;			
	}
	
	/* - SINLGE CLICK - */
	if(!secondClick){ //  button 1 pressed for the first time 
		HLboolean isTouching;
		/* if the stylus is touching a node then pick it up */
		/* give priority to nodes */
		if(lastTouchedNodeID != HL_OBJECT_ANY){
			hlGetShapeBooleanv(lastTouchedNodeID,HL_PROXY_IS_TOUCHING,&isTouching);
			if(isTouching == HL_TRUE || (stillTouchingLastMovedElement&&lastMovedElement == lastTouchedNodeID)){
				draggedElementID = lastTouchedNodeID;
				try{
					CollectionsManager::NodeData & nd = collectionsManager->getNodeDataFromID(lastTouchedNodeID);
					executeCommand('c',nd.diagramId,0,0,0,0); // pick up command
				}catch(int){
					stopExecution("Button 1 callback: Could not retrieve node from ID");
				}
				return;
			}
		}
		/* no node is currently touched, check the edges now */
		if(collectionsManager->isEdge(lastTouchedEdgeID)){
			hlGetShapeBooleanv(lastTouchedEdgeID,HL_PROXY_IS_TOUCHING,&isTouching);
			if(isTouching == HL_TRUE||(stillTouchingLastMovedElement&&lastMovedElement == lastTouchedEdgeID)){
				draggedElementID = lastTouchedEdgeID;
				try{
					CollectionsManager::EdgeData & ed = collectionsManager->getEdgeDataFromID(lastTouchedEdgeID);
					executeCommand('c',ed.diagramId,0,0,0,0); // pick up command
				}catch(int){
					stopExecution("Button 1 callback: Could not retrieve edge from ID");
				}
			}
		}
	}else{ // button 1 pressed for the second time 
		if(springStatus != DRAGGING)
			return;
		springStatus = STOP_DRAGGING;
		HLdouble leanPoint[3];
		hlGetDoublev(HL_PROXY_POSITION,leanPoint);
		leanPoint[2] = 1;
		hduVector3Dd winCoordPoint;				
		toScreen(hduVector3Dd(leanPoint),winCoordPoint);
		
		try{
			if(collectionsManager->isNode(draggedElementID)){
				CollectionsManager::NodeData & nd = collectionsManager->getNodeDataFromID(draggedElementID);
				movedNode = true;
				executeCommand('m',nd.diagramId,winCoordPoint[0],collectionsManager->getScreenHeight() - winCoordPoint[1],0,0);
			}else if(collectionsManager->isEdge(draggedElementID)){ // double check necessary for the user might have changed the tab or closed the window before releasing the spring 		
				springStart[2] = 1;
				hduVector3Dd startWinCoordPoint;
				toScreen(hduVector3Dd(springStart),startWinCoordPoint);//it's an edge, we also need the point where the motion has started
				CollectionsManager::EdgeData & ed = collectionsManager->getEdgeDataFromID(draggedElementID);
				executeCommand('m', ed.diagramId, 
					winCoordPoint[0],
					collectionsManager->getScreenHeight() - winCoordPoint[1],
					startWinCoordPoint[0],
					collectionsManager->getScreenHeight() - startWinCoordPoint[1]
				);
			}
		}catch(int){
			stopExecution("Button 1 callback: Could not retrieve element from ID");
		}
		/* with second click we place the element and it becomes the last moved element */
		lastMovedElement = draggedElementID;
		secondClick = false;
	}
}

void HapticManager::setAttractTo(jint hapticId){
	if(attractionStatus == NO_ATTRACTION) // starts only if there is no previous attraction being overwritten
		attractionStatus = START_ATTRACTION;	
	attractToHapticId = hapticId;
	alreadyTouchingAttractingElement = false;
}

void HapticManager::pickUp(jint hapticId){
	springStatus = START_DRAGGING;
	secondClick = true;
	hlGetDoublev(HL_PROXY_POSITION,springStart);
}

bool HapticManager::wasAlreadyTouchingAttractingElement(){
	return alreadyTouchingAttractingElement;
}

void HapticManager::draw(void){
	// Start haptic frame.  (Must do this before rendering any haptic shapes.)
    hlBeginFrame();   

	if(!doneOnce){
		hlStartEffect(HL_EFFECT_FRICTION, frictionFX);
		doneOnce = true;
	}

	if(wall){
		// Plane shape.
		hlPushAttrib(HL_MATERIAL_BIT|HL_TOUCH_BIT);
		hlBeginShape(HL_SHAPE_FEEDBACK_BUFFER, wall1Id);
		hlMaterialf(HL_FRONT_AND_BACK, HL_STIFFNESS, 1);
		hlMaterialf(HL_FRONT, HL_POPTHROUGH, 0 );
		hlMaterialf(HL_FRONT, HL_STATIC_FRICTION, 0);
		hlMaterialf(HL_FRONT, HL_DYNAMIC_FRICTION, 0);
		hlTouchModel(HL_CONTACT);
		hlTouchableFace(HL_FRONT);
		drawPlane(-0.005);
		hlEndShape();
#define SECOND_PLANE
#ifdef SECOND_PLANE
		hlBeginShape(HL_SHAPE_FEEDBACK_BUFFER, wall2Id);
		hlMaterialf(HL_BACK, HL_STIFFNESS, 1);
		hlTouchableFace(HL_BACK);
		hlMaterialf(HL_FRONT, HL_POPTHROUGH, 0 );
		hlMaterialf(HL_FRONT, HL_STATIC_FRICTION, 0);
		hlMaterialf(HL_FRONT, HL_DYNAMIC_FRICTION, 0);
		drawPlane(0.005);
		hlEndShape();
#endif
		hlPopAttrib();
	}

    // Start a new haptic shape.  Use the feedback buffer to capture OpenGL 
    // geometry for haptic rendering.

	// draw nodes
	int size = collectionsManager->getNodesNum();
	
	for(int i=0; i< size;i++){
		CollectionsManager::NodeData & nd = collectionsManager->getNodeData(i);

		hlPushAttrib(HL_HINT_BIT);
		hlHinti(HL_SHAPE_FEEDBACK_BUFFER_VERTICES, 3);
		hlBeginShape(HL_SHAPE_FEEDBACK_BUFFER, nd.hapticId);
		 hlTouchModel(HL_CONSTRAINT);
		 hlTouchModelf(HL_SNAP_DISTANCE ,  4.0f );
		 hlMaterialf(HL_FRONT_AND_BACK, HL_STIFFNESS, 1.0);
		 hlMaterialf(HL_FRONT_AND_BACK, HL_DAMPING, 0);

		 hlMaterialf(HL_FRONT_AND_BACK, HL_STATIC_FRICTION, 0);
		 hlMaterialf(HL_FRONT_AND_BACK, HL_DYNAMIC_FRICTION, 0);

		 //draw the point
		 glBegin(GL_POINTS);
		  glVertex3d(nd.x,nd.y,0);
		 glEnd();
		 
		// End the shape.
		hlEndShape();
		hlPopAttrib();

		// saves the coorinates for a later use so that for the effect we don't need to cycle again
		if(attractionStatus == START_ATTRACTION || attractionStatus == DOING_ATTRACTION){
			if(nd.hapticId == attractToHapticId){
				attractToCoord[0] = nd.x;
				attractToCoord[1] = nd.y;
				attractToCoord[2] = 0;
			}
		}
	}

	/*  draw lines. When a node is moved, edges are not drawn for the very first haptic frame after the shift.   *
	 *  This is to address the behaviour of the device which, after a n ode is moved cannot see the styilus     *
	 *  touching the edge. As a consequence of that in hlMotionCB() when moving away from a node along an edge  *
	 *  no edge name will be spoken and furthermore the pick up botton won't work as the device thinks it's not *  
	 *  touching anything. Not drawing the edge on the first frame, seems to address this issue.                */
	if(!movedNode) {
		size = collectionsManager->getEdgesNum();
		for(int i = 0; i<size; i++){
			CollectionsManager::EdgeData & ed = collectionsManager->getEdgeData(i);
			hlPushAttrib(HL_HINT_BIT|HL_MATERIAL_BIT);
			hlHinti(HL_SHAPE_FEEDBACK_BUFFER_VERTICES, ed.getSize());

			hlBeginShape(HL_SHAPE_FEEDBACK_BUFFER, ed.hapticId);
			hlTouchModel(HL_CONSTRAINT);
			hlTouchModelf(HL_SNAP_DISTANCE, ( (stickyMode && springStatus != DRAGGING && springStatus != START_DRAGGING)  ? 20.0f : 3.5f ));
			hlMaterialf(HL_FRONT_AND_BACK, HL_STIFFNESS, 1);
			
			if(ed.stipplePattern == 0xFFFF){
				hlMaterialf(HL_FRONT_AND_BACK, HL_STATIC_FRICTION, 0.5f);
			}

			glLineWidth(1.0);
			
			glBegin(GL_LINES);
			 for(unsigned int j = 0; j < ed.getSize(); j++){
				for(unsigned int k = j; k < ed.getSize(); k++){
					if(ed.adjMatrix[j][k]){
						if(  k >= ed.nodeStart && 
							   (!stickyMode || (springStatus != RELEASED && springStatus != STOP_DRAGGING))
							   && attractionStatus != DOING_ATTRACTION && attractionStatus != START_ATTRACTION){ // not a line connecting two edges and stickyMode not enabled
							/* it's drawing a line from a point to a node, thus it draws it a little shorter*/
							double hypotenuse = sqrt( (ed.x[j] - ed.x[k])*(ed.x[j] - ed.x[k]) + (ed.y[j] - ed.y[k])*(ed.y[j] - ed.y[k]));		
							if(hypotenuse == 0)
								continue;
							double mySin = (ed.y[j] - ed.y[k])/hypotenuse;
							double myCos = (ed.x[j] - ed.x[k])/hypotenuse; 
							glVertex3d(ed.x[k]+(EDGE_SHORTEND_VAL * myCos) , ed.y[k]+(EDGE_SHORTEND_VAL*mySin),0);
							if(j >= ed.nodeStart) //its a direct edge connecting two nodes 
								glVertex3d(ed.x[j]+(EDGE_SHORTEND_VAL * -myCos),ed.y[j]+(EDGE_SHORTEND_VAL*-mySin),0);
							else
								glVertex3d(ed.x[j],ed.y[j],0);
						}else{
							glVertex3d(ed.x[j],ed.y[j],0);
							glVertex3d(ed.x[k],ed.y[k],0);
						}
					}
				}
			}
			glEnd();
			hlEndShape();
			hlPopAttrib();

			// saves the coorinates for a later use so that for the effect we don't need to cycle again
			if(attractionStatus == START_ATTRACTION || attractionStatus == DOING_ATTRACTION){
				if(ed.hapticId == attractToHapticId){
					attractToCoord[0] = ed.attractPoint[0];
					attractToCoord[1] = ed.attractPoint[1];
					attractToCoord[2] = 0;
				}
			}
		}
	}else {
		movedNode = false;
	}
	/* ---  FORCES --- */
	/* friction */
	if(lastTouchedEdgeID != HL_OBJECT_ANY){
		/* check if the edge is still there, it might not as the tab could be changed */
		if(collectionsManager->isEdge(lastTouchedEdgeID)){
			//hlGetShapeBooleanv(lastTouchedEdgeID,HL_PROXY_IS_TOUCHING,&isTouching); FIXME to remove
			if(frictionDisabled||lastTouchedEdgeIsUntouched/*(isTouching == HL_FALSE)*/){
					hlEffectd(HL_EFFECT_PROPERTY_MAGNITUDE, 0);
					hlEffectd(HL_EFFECT_PROPERTY_GAIN, 0);
				}else{
					if(lastTouchedEdgeStipplePattern == 0xAAAA){ // dotted
						hlEffectd(HL_EFFECT_PROPERTY_MAGNITUDE, 0.4f);
						hlEffectd(HL_EFFECT_PROPERTY_GAIN, 1);
					}else if(lastTouchedEdgeStipplePattern == 0xF0F0){ // dashed
						hlEffectd(HL_EFFECT_PROPERTY_MAGNITUDE, 0.6);
						hlEffectd(HL_EFFECT_PROPERTY_GAIN, 0.6);
					}
			}
		}else{
			lastTouchedEdgeID = HL_OBJECT_ANY;
			hlEffectd(HL_EFFECT_PROPERTY_MAGNITUDE, 0);
			hlEffectd(HL_EFFECT_PROPERTY_GAIN, 0);
		}
		hlUpdateEffect(frictionFX); 
	}

	// spring 
	if(springStatus == START_DRAGGING){
		hlPushAttrib(HL_EFFECT_BIT);
		hlEffectd(HL_EFFECT_PROPERTY_GAIN, 0.3);
		hlEffectd(HL_EFFECT_PROPERTY_MAGNITUDE, 0.5);
		
		hduMatrix worldToView;
		hduMatrix graphicToTouch;
		hduMatrix touchToWorkspace;
		hduMatrix WorldToDevice;
		glMatrixMode(GL_MODELVIEW);
		glGetDoublev(GL_MODELVIEW_MATRIX,worldToView);
		hlGetDoublev(HL_VIEWTOUCH_MATRIX, graphicToTouch );
		hlGetDoublev(HL_TOUCHWORKSPACE_MATRIX, touchToWorkspace );
		WorldToDevice = worldToView * graphicToTouch * touchToWorkspace;

		hduVector3Dd dst;
		WorldToDevice.multVecMatrix(hduVector3Dd(springStart[0],springStart[1],0),dst);
		
		hlEffectdv(HL_EFFECT_PROPERTY_POSITION, dst);
		hlStartEffect(HL_EFFECT_SPRING, springFX);
		hlPopAttrib();
		springStatus = DRAGGING;
	}else if(springStatus == STOP_DRAGGING){
		hlStopEffect(springFX);
		springStatus = RELEASED;
	}
	
	// attraction 
	HLboolean isTouching = HL_FALSE;
	if(attractionStatus == DOING_ATTRACTION || attractionStatus == START_ATTRACTION){
		/* we need to be sure we're currently touching the element */
		if(attractToHapticId != HL_OBJECT_ANY){
			hlGetShapeBooleanv(attractToHapticId,HL_PROXY_IS_TOUCHING,&isTouching);
		}
		
		// when touching the element we're getting attracted to, then shut the force down
		if(isTouching == HL_TRUE){ 
			if(attractionStatus == DOING_ATTRACTION){ // we finally reached the attracting node
				attractionStatus = STOP_ATTRACTION;
			}else{
				attractionStatus = NO_ATTRACTION; // we were already touching the node we wanted to get attracted to 
				alreadyTouchingAttractingElement = true;
			}
		}
	}

	if(attractionStatus == STOP_ATTRACTION){
		attractionStatus = NO_ATTRACTION;
	    hlStopEffect(attractionFX);
	} else if(attractionStatus == START_ATTRACTION || attractionStatus == DOING_ATTRACTION){
		HLdouble devicePosition[3];
		HDdouble direction[3];
		hlGetDoublev(HL_DEVICE_POSITION,devicePosition);
		direction[0] = attractToCoord[0] - devicePosition[0];
		direction[1] = attractToCoord[1] - devicePosition[1];
		direction[2] = attractToCoord[2] - devicePosition[2];
		HDdouble directionNorm = norm(direction);
		if(directionNorm != 0 ){
			direction[0] /= directionNorm;
			direction[1] /= directionNorm;
			direction[2] /= directionNorm;
		}
		hlPushAttrib(HL_EFFECT_BIT);
		hlEffectdv(HL_EFFECT_PROPERTY_DIRECTION, direction);
		hlEffectd(HL_EFFECT_PROPERTY_GAIN, 0.5f);
		hlEffectd(HL_EFFECT_PROPERTY_MAGNITUDE, 0.5f);
		if(attractionStatus == START_ATTRACTION){
			hlStartEffect(HL_EFFECT_CONSTANT, attractionFX);
			attractionStatus = DOING_ATTRACTION;
		}else{
			hlUpdateEffect(attractionFX);
		}
		hlPopAttrib();
	}
#ifdef VIBRATION
	if(vibrationStatus == START_VIBRATION){
		hlStartEffect(HL_EFFECT_CALLBACK, vibrationFX);
		vibrationEnd = clock() + VIBRATION_DURATION;
		vibrationStatus = DOING_VIBRATION;
	}else if(vibrationStatus == DOING_VIBRATION){
		if(clock() >= vibrationEnd){
			hlStopEffect(vibrationFX);
			vibrationStatus = NO_VIBRATION;
		}
	}
#endif
    // End the haptic frame.
    hlEndFrame();
}

void HapticManager::drawPlane(float zoffset){
	GLuint displayList = (zoffset < 0) ? displayList1 : displayList2;

	if (displayList){
		glCallList(displayList);
	}
	else{
		if(zoffset < 0){
			displayList1 = glGenLists(1);
			glNewList(displayList1, GL_COMPILE_AND_EXECUTE);
		}else{
			displayList2 = glGenLists(1);
			glNewList(displayList2, GL_COMPILE_AND_EXECUTE);
		}	
		glNewList(displayList, GL_COMPILE_AND_EXECUTE);
		glPushAttrib(GL_ENABLE_BIT);
		glPolygonOffset(1,1);
		glEnable(GL_POLYGON_OFFSET_FILL);
		
		glBegin(GL_QUADS);
		glVertex3f(-5, -5, zoffset);
		glVertex3f(5, -5, zoffset);
		glVertex3f(5, 5, zoffset);
		glVertex3f(-5, 5, zoffset);
		glEnd();
		glDisable(GL_POLYGON_OFFSET_FILL);
		glPopAttrib();
		glEndList();
	}
	
}

HHD HapticManager::ghHD = HD_INVALID_HANDLE;
HHLRC HapticManager::ghHLRC = 0;
const double HapticManager::EDGE_SHORTEND_VAL = 0.06;
const unsigned int HapticManager::CLICK_INTERVAL = CLOCKS_PER_SEC/5;
const double HapticManager::EDGE_SPEECH_DISTANCE_WHEN_LEAVING_NODE = 0.03;
#ifdef VIBRATION
const HDdouble HapticManager::VIBRATION_AMPLITUDE = 0.88;//0.88;
const HDint HapticManager::VIBRATION_FREQUENCY = 170;//160;
const unsigned int HapticManager::VIBRATION_DURATION = CLOCKS_PER_SEC/10; // = 100 ms
#endif