comparison native/Falcon/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
children
comparison
equal deleted inserted replaced
4:2c67ac862920 5:d66dd5880081
1 /*
2 CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
3
4 Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
5
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "HapticManager.h"
21
22 using namespace HAPI;
23
24 // Render a sphere using OpenGL calls.
25 void drawSphere() {
26 GLUquadricObj* pObj = gluNewQuadric();
27 gluSphere(pObj, 0.0010, 10, 10);
28 gluDeleteQuadric(pObj );
29 }
30
31 HapticManager::HapticManager(CollectionsManager * cManager,
32 void (*func)(
33 const jchar cmd,
34 const jint ID,
35 const jdouble startx,
36 const jdouble starty,
37 const jdouble endx,
38 const jdouble endy)
39 )
40 : executeCommand(func), cm(cManager), force_effect(0), last_touched_point(NULL),
41 last_touched_line(NULL),magnetic_mode(LOOSE), pickup_mode(RELEASED), object_unselected(false),
42 magnetic_mode_changed(false), pickedup_point(NULL), pickedup_line(NULL) {}
43
44
45 bool HapticManager::init (void){
46
47 hd = new AnyHapticsDevice();
48
49 // The haptics renderer to use.
50 hd->setHapticsRenderer( new HAPI::GodObjectRenderer() );
51
52 /* init the device */
53 if(hd->initDevice() != HAPIHapticsDevice::SUCCESS){
54 return false;
55 }
56
57 /* enable the device ( forces and positions will be updated) */
58 hd->enableDevice();
59
60 return true;
61 }
62
63 /* draws the visual diagram every time it's called. The haptic diagram is redrawn only if *
64 * something has changed in the diagram ( redraw_haptic_scene = true ) as, unlike openGL, *
65 * it doesn't need to be drawn at each frame but only once */
66 void HapticManager::drawDiagram(bool redraw_haptics_scene, bool pickup, int _attract_to ){
67
68 /* STOP_ATTRACTION means the object has been reached (or eventually deleted). The attraction *
69 * force must therefore be stopped and a redrawing of the haptic forces is necessary */
70 if(attraction_mode == STOP_ATTRACTION){
71 attraction_mode = NO_ATTRACTION;
72 attract_to = NO_ID;
73 redraw_haptics_scene = true;
74 }
75
76 /* _attract_to is different from NO_ID there is no attraction going on (attraction_mode = NO_ATTRACTION)*
77 * It differs from STOP_ATTRACTION in that the haptic scene doesn't have to be repainted */
78 if(_attract_to != NO_ID){
79 attract_to = _attract_to;
80 attraction_mode = ACTIVE;
81 redraw_haptics_scene = true;
82 }
83
84 if(magnetic_mode_changed){
85 redraw_haptics_scene = true;
86 magnetic_mode_changed = false;
87 }
88
89 if(pickup){
90 pickup_mode = START_DRAGGING;
91 redraw_haptics_scene = true;
92 }
93
94 /* wash before use */
95 if(redraw_haptics_scene){
96 point_set.clear();
97 line_set.clear();
98 point_id_map.clear();
99 line_id_map.clear();
100 }
101
102 /* --- draw the edges --- */
103 glPushAttrib(GL_ENABLE_BIT);
104 glColor3f(1.0f,0.0f,0.0f); // Red
105 glLineWidth(2.0);
106 glDisable(GL_LIGHTING);
107 glEnable(GL_COLOR_MATERIAL);
108 int numEdges = cm->getEdgesNum();
109 for ( int i = 0; i < numEdges; i++){
110 /* draw the edge in openGL */
111 CollectionsManager::EdgeData & ed = cm->getEdgeData(i);
112 glPushAttrib(GL_ENABLE_BIT);
113 glLineStipple(1, ed.stipplePattern);
114 glEnable(GL_LINE_STIPPLE);
115 glBegin(GL_LINES);
116 for(unsigned int j = 0; j < ed.getSize(); j++){
117 for(unsigned int k = j; k < ed.getSize(); k++){
118 if(ed.adjMatrix[j][k]){
119 glVertex3d(ed.x[j]*2,ed.y[j]*GRAPHIC_SCALE,0);
120 glVertex3d(ed.x[k]*2,ed.y[k]*GRAPHIC_SCALE,0);
121 }
122 }
123 }
124 glEnd();
125 glPopAttrib();
126 /* update haptics edge line set if a change in the collection occurred */
127 if(redraw_haptics_scene){
128 /* build the vector with the edges*/
129 for(unsigned int j = 0; j < ed.getSize(); j++){
130 for(unsigned int k = j; k < ed.getSize(); k++){
131 if(ed.adjMatrix[j][k]){
132 line_set.push_back(Collision::LineSegment (
133 Vec3(ed.x[j],ed.y[j],0),
134 Vec3(ed.x[k],ed.y[k],0)
135 ));
136 }
137 }
138 }
139
140 for(vector< HAPI::Collision::LineSegment>::iterator itr = line_set.begin(); itr != line_set.end(); itr++){
141 line_id_map.insert(pair<Collision::LineSegment*,int>(&(*itr),ed.hapticId));
142 }
143 }
144 }
145 glPopAttrib();
146
147 /* --- draw the nodes --- */
148 int numNodes = cm->getNodesNum();
149 glColor3f(1.0f, 1.0f, 1.0f); // white
150 for( int i = 0; i < numNodes; i++){
151 /* draw the nodes in openGL */
152 glPushMatrix();
153 CollectionsManager::NodeData &nd = cm->getNodeData(i);
154 glTranslated(nd.x*GRAPHIC_SCALE,nd.y*GRAPHIC_SCALE, 0);
155 drawSphere();
156 glPopMatrix();
157
158 /* update haptics node line set if a change in the collection occurred */
159 if(redraw_haptics_scene){
160 point_set.push_back(Vec3(nd.x,nd.y,0));
161 point_id_map.insert(pair<Collision::Point*,int>(&(point_set.back()),nd.hapticId)) ;
162 }
163 }
164
165 /* --- update the haptic force if a change in the collection occurred --- */
166 if(redraw_haptics_scene){
167 /* reset the effects and set up new one, otherwise they would accumulate */
168 hd->clearEffects();
169 /* first lines */
170 if(!line_set.empty()){
171 /* force according to the current attraction_mode */
172 int force_factor = (magnetic_mode == STICKY) ? LINE_FORCE_FACTOR_STICKY : LINE_FORCE_FACTOR_LOOSE;
173 /* if the user is dragging or looking for an object, the force *
174 * factor is always loose until she drops or find the object */
175 if(pickup_mode == DRAGGING || pickup_mode == START_DRAGGING || attraction_mode == ACTIVE){
176 force_factor = LOOSE;
177 }
178
179 /* update the force */
180 force_effect.reset( new HapticShapeConstraint(new HapticLineSet( line_set, 0 ),force_factor));
181 hd->addEffect(force_effect.get());
182
183 /* recalculate last_touched_line if it was != NULL as the pointed *
184 object is now destroyed after clear() and replaced with a new one */
185 if(last_touched_line != NULL){
186 last_touched_line = NULL;
187 vector<Collision::LineSegment>::iterator itr;
188 Vec3 closest_point, normal, tex_coord;
189 for( itr=line_set.begin(); itr != line_set.end(); itr++ ){
190 itr->closestPoint(proxy_pos,closest_point,normal,tex_coord);
191 if(pointsDistance(closest_point,proxy_pos) < LINE_COLLISION_THRESHOLD){
192 last_touched_line = &(*itr);
193 break;
194 }
195 }
196 if(last_touched_line == NULL){
197 /* touched line was != NULL and now is NULL. It means it has been deleted *
198 * while being touched, therefore an unselect command must be issued */
199 object_unselected = true;
200 }
201 }
202 }else if(last_touched_line != NULL){
203 /* if last_touuched_line was != null it means an edge has been deleted *
204 * which was being touched, therefore un unselect command must be issued */
205 object_unselected = true;
206 /* if there are no lines there cannot be a last touched one */
207 last_touched_line = NULL;
208 }
209
210 /* now points */
211 if(!point_set.empty()){
212 /* update the force */
213 force_effect.reset( new HapticShapeConstraint(new HapticPointSet( point_set, 0 ),POINT_FORCE_FACTOR ) );
214 hd->addEffect(force_effect.get());
215
216 /* recalculate lastTouchedNode if it was != NULL as the pointed object is now destroyed after clear() */
217 if(last_touched_point != NULL){
218 last_touched_point = NULL;
219 vector<Collision::Point>::iterator itr;
220 for(itr=point_set.begin(); itr != point_set.end(); itr++ ){
221 if(pointsDistance(itr->position,proxy_pos) < POINT_COLLISION_THRESHOLD ){
222 last_touched_point = &(*itr);
223 break;
224 }
225 }
226 if(last_touched_point == NULL){
227 /* touched point was != NULL and now is NULL. It means it has been deleted *
228 * while being touched, therefore an unselect command must be issued */
229 object_unselected = true;
230 }
231 }
232 }else if(last_touched_point != NULL){
233 /* if last_touuched_point was != null it means a node has been deleted *
234 * which was being touched, therefore un unselect command must be issued */
235 object_unselected = true;
236 /* if there are no points there cannot be a last touched one */
237 last_touched_point = NULL;
238 }
239
240 if(pickup_mode == START_DRAGGING && attraction_mode == NO_ATTRACTION){
241 force_effect.reset(new HapticSpring(proxy_pos,SPRING_FORCE_FACTOR));
242 hd->addEffect(force_effect.get());
243 /* spring force is initialized not pickup_mode goes to DRAGGING*/
244 pickup_mode = DRAGGING;
245 }else if(attraction_mode == ACTIVE){
246 bool found = false;
247 /* let's look for the object into the nodes */
248 for(map<HAPI::Collision::Point*,int>::iterator itr = point_id_map.begin();itr != point_id_map.end(); itr++){
249 if( (*itr).second == attract_to ){
250 force_effect.reset(new HapticSpring(((*itr).first)->position,SPRING_FORCE_FACTOR));
251 hd->addEffect(force_effect.get());
252 found = true;
253 break;
254 }
255 }
256 /* if not found look into the edges */
257 if(!found){
258 for(map<HAPI::Collision::LineSegment*,int>::iterator itr = line_id_map.begin();itr != line_id_map.end(); itr++){
259 if( (*itr).second == attract_to ){
260 HAPI::Collision::LineSegment* line_ptr = (*itr).first;
261 force_effect.reset(new HapticSpring(
262 midPoint(line_ptr->start, line_ptr->end),
263 SPRING_FORCE_FACTOR));
264 hd->addEffect(force_effect.get());
265 found = true;
266 break;
267 }
268 }
269 }
270
271 if(!found){
272 /* element has been deleted before user could reach it: go back to normal */
273 attraction_mode = STOP_ATTRACTION;
274 }
275
276 }
277 /* just deallocate the memory for the last force effect */
278 force_effect.reset();
279 /* tranfer all the forces to the device */
280 hd->transferObjects();
281 }
282 }
283
284
285 void HapticManager::drawCursor(){
286 HAPIHapticsRenderer *hr = hd->getHapticsRenderer();
287 if( hr ) {
288 /* save the proxy pos in a global variable */
289 proxy_pos = static_cast< HAPIProxyBasedRenderer * >(hr)->getProxyPosition();
290
291 glPushMatrix();
292 glPushAttrib(GL_CURRENT_BIT | GL_ENABLE_BIT | GL_LIGHTING_BIT);
293 glTranslatef( (GLfloat)proxy_pos.x*GRAPHIC_SCALE,
294 (GLfloat)proxy_pos.y*GRAPHIC_SCALE,
295 (GLfloat)proxy_pos.z );
296 glEnable(GL_COLOR_MATERIAL);
297 glColor3f(0.0f, 0.5f, 1.0f);// Blue
298 drawSphere();
299 glPopAttrib();
300 glPopMatrix();
301 }
302 }
303
304 bool HapticManager::checkNodeCollision(){
305 /* if the proxy goes far enough from the last touched node, then *
306 * lastTouched node can be set back to NULL */
307 bool do_unselect = false;
308 if(last_touched_point != NULL){
309 if(pointsDistance(last_touched_point->position, proxy_pos) > POINT_COLLISION_THRESHOLD){
310 last_touched_point = NULL;
311 do_unselect = true;
312 }
313 }
314
315 /* find the closest point among those closer than POINT_COLLISION_THRESHOLD */
316 vector<Collision::Point>::iterator itr;
317 HAPI::Collision::Point* found_point = NULL;
318 double min_dist = POINT_COLLISION_THRESHOLD;
319 double dist;
320 for(itr=point_set.begin(); itr != point_set.end(); itr++ ){
321 if((dist = pointsDistance(itr->position,proxy_pos)) < min_dist ){
322 found_point = &(*itr);
323 min_dist = dist;
324 }
325 }
326
327 if(found_point != NULL){
328 /* if the last touched point is the same, it means the cursor was *
329 * already on it therefore do nothing as the node name is uttered only when *
330 * the proxy comes across it and not at each frame (it would make a mess) */
331 if(last_touched_point != found_point){
332 last_touched_point = found_point;
333 /* Setting last_touched_line to NULL, means untouching the eventual touching line. *
334 * When a node is touched, the connected lines are indeed untouched as only one object *
335 * at time can be touching the position proxy */
336 last_touched_line = NULL;
337 int point_id = point_id_map[last_touched_point];
338 /* select the node for eventual highlighting int the java tree */
339 executeCommand(SELECT_CMD,point_id,0,0,0,0);
340 /* utter the name of the node */
341 executeCommand(SPEAK_NAME_CMD,point_id,0,0,0,0);
342 if(attraction_mode == ACTIVE && point_id == attract_to){
343 /* we reached the attracting node, stop the attraction force */
344 attraction_mode = STOP_ATTRACTION;
345 }
346 }
347 return true;
348 }
349
350 if(do_unselect)
351 executeCommand(UNSELECT_CMD,0,0,0,0,0);
352
353 return false;
354 }
355
356 bool HapticManager::checkEdgeCollision(){
357 Vec3 closest_point, normal, tex_coord;
358
359 /* id is taken from the last touched line is overwritten. To understand why suppose we have an edge *
360 * made of two lines A and B. When going from one line to another we don't have to utter the name (see *
361 * comment below. last_touched_line goes from a to NULL before being set to B. If lastTouchedLineId is *
362 * calculated before checking for NULL-ness it will become -1 and it won't be useful anymore to avoid *
363 * uttering the edge name when going from A to B. In this way instead proxy goes from A to B, the id is *
364 * to A's, last_touched_line becomes NULL and then it becomes B, but the name is not uttered because id *
365 * is the same. This is a very long comment. I want to finish the line to make it look better. bye bye */
366 int lastTouchedLineId = (last_touched_line == NULL) ? - 1 : line_id_map[last_touched_line];
367
368 bool do_unselect = false;
369 /* if the proxy goes far enough from the last touched line, then last_touched_line can be set back to NULL */
370 if(last_touched_line != NULL){
371 last_touched_line->closestPoint(proxy_pos,closest_point,normal,tex_coord);
372 if(pointsDistance(closest_point, proxy_pos) > LINE_COLLISION_THRESHOLD){
373 last_touched_line = NULL;
374 do_unselect = true;
375 }
376 }
377
378 /* find the closest line to the proxy among those closer than LINE_COLLISION_THRESHOLD */
379 vector<Collision::LineSegment>::iterator itr;
380 HAPI::Collision::LineSegment* found_line = NULL;
381 double min_dist = LINE_COLLISION_THRESHOLD;
382 double dist;
383 for( itr=line_set.begin(); itr != line_set.end(); itr++ ){
384 itr->closestPoint(proxy_pos,closest_point,normal,tex_coord);
385 if((dist = pointsDistance(closest_point,proxy_pos)) < min_dist){
386 found_line = &(*itr);
387 min_dist = dist;
388 }
389 }
390
391 if(found_line != NULL){
392 last_touched_line = found_line;
393 /* proxy can touch only one object at time either line or point */
394 last_touched_point = NULL;
395 /* An edge can be broken into several lines. Such lines will map to the same edge id *
396 * If the proxy detouches from a line but immediately touches another line mapped to *
397 * the same id, it just means the proxy is going aling the line and therefore no *
398 * name must be uttered again (the name must be uttered when touching an edge and *
399 * not when touching each line. We keep track of the last touched id and if it's the *
400 * as te new touched line id, then nothing is uttered, for we're on the same edge */
401 if(lastTouchedLineId != line_id_map[last_touched_line]){
402 int line_id = line_id_map[last_touched_line];
403 /* select the node for eventual highlighting int the java tree */
404 executeCommand(SELECT_CMD,line_id,0,0,0,0);
405 /* utter the name of the edge */
406 executeCommand(SPEAK_NAME_CMD,line_id,0,0,0,0);
407 if(attraction_mode == ACTIVE && line_id == attract_to){
408 /* we reached the attracting node, stop the attraction force */
409 attraction_mode = STOP_ATTRACTION;
410 }
411 }
412 return true;
413 }
414
415 if(do_unselect){
416 executeCommand(UNSELECT_CMD,0,0,0,0,0);
417 }
418
419 return false;
420 }
421
422 bool HapticManager::checkUnselection(void){
423 if(object_unselected){
424 object_unselected = false;
425 executeCommand(UNSELECT_CMD,0,0,0,0,0);
426 return true;
427 }
428 return false;
429 }
430
431 bool HapticManager::checkButtons(void){
432 HAPIInt32 status = hd->getButtonStatus();
433 /* handle the buttons. Buttons can be pressed one at time. If a button is pressed all the *
434 * other buttons (either when pressed or released) are ignored untill the button is released */
435 if(pressed_button == NO_BUTTON){
436 if(status & FRONT_BUTTON){
437 /* front button pressed, change how magnetic the lines are */
438 pressed_button = FRONT_BUTTON;
439 /* switch from sticky to loose and vice versa */
440 magnetic_mode = (magnetic_mode == STICKY) ? LOOSE : STICKY;
441 magnetic_mode_changed = true;
442 /* make a sound according to the new attraction mode */
443 executeCommand(PLAY_SOUND_CMD, (magnetic_mode == STICKY) ? STICKY_MODE_SOUND : LOOSE_MODE_SOUND ,0,0,0,0);
444 return true;
445 }else if(status & REAR_BUTTON){
446 pressed_button = REAR_BUTTON;
447 /* rear button pressed: First time it picks up an object, second time it drops it */
448 if(pickup_mode == RELEASED){ // pickup mode was released, then now user picked up an object
449 if(last_touched_point != NULL){
450 pickedup_point = last_touched_point;
451 last_dragging_pos = pickedup_point->position; // last_dragging_pos used in checkMotion
452 /* send a pickup command to the Java thread which will in turn issue another pickup *
453 * command to this thread (pickup = true in drawDiagram) if the lock could be granted */
454 executeCommand(PICKUP_CMD,point_id_map[last_touched_point],0,0,0,0);
455 }else if(last_touched_line != NULL){
456 pickedup_line = last_touched_line;
457 pickup_line_pos = proxy_pos; // the point where the line was picked up
458 last_dragging_pos = proxy_pos;// last_dragging_pos used in checkMotion
459 executeCommand(PICKUP_CMD,line_id_map[last_touched_line],0,0,0,0);
460 }/* else user picked up thin air. Do nothing. */
461 }else if(pickup_mode == DRAGGING){ // pickup mode was dragging, then now user dropped an object
462 pickup_mode = RELEASED;
463 Vec3 & new_position = hapticToScreenSpace(Vec3(proxy_pos),
464 cm->getScreenWidth(),
465 cm->getScreenHeight());
466 if(pickedup_point){
467 executeCommand(MOVE_CMD,point_id_map[pickedup_point],new_position.x,new_position.y,0,0);
468 pickedup_point = NULL;
469 }else{
470 hapticToScreenSpace(pickup_line_pos,
471 cm->getScreenWidth(),
472 cm->getScreenHeight());
473 executeCommand(MOVE_CMD,
474 line_id_map[pickedup_line],
475 new_position.x,
476 new_position.y,
477 pickup_line_pos.x,
478 pickup_line_pos.y);
479 pickedup_line = NULL;
480 }
481 return true;
482 }
483 } else if(status & ( LEFT_BUTTON | RIGHT_BUTTON )){ // either left or right button pressed
484 pressed_button = (status == LEFT_BUTTON) ? LEFT_BUTTON : RIGHT_BUTTON;
485 if(last_touched_point != NULL){ // priority to nodes
486 executeCommand(SPEAK_INFO_CMD,point_id_map[last_touched_point], 0,0,0,0);
487 return true;
488 }
489 if(last_touched_line != NULL){
490 executeCommand(SPEAK_INFO_CMD,line_id_map[last_touched_line], 0,0,0,0);
491 return true;
492 }
493 }
494 } else if ( /* deactivate the buttons */
495 (pressed_button == FRONT_BUTTON && !(status & FRONT_BUTTON)) ||
496 (pressed_button == REAR_BUTTON && !(status & REAR_BUTTON)) ||
497 (pressed_button == LEFT_BUTTON && !(status & LEFT_BUTTON)) ||
498 (pressed_button == RIGHT_BUTTON && !(status & RIGHT_BUTTON)))
499 {
500 pressed_button = NO_BUTTON;
501 }
502 return false;
503 }
504
505 bool HapticManager::checkMotion(void){
506 if(pickup_mode != DRAGGING)
507 return false;
508 if(pointsDistance(proxy_pos,last_dragging_pos) > DRAGGING_SOUND_THRESHOLD){
509 last_dragging_pos = proxy_pos;
510 executeCommand(PLAY_SOUND_CMD,DRAGGING_SOUND,0,0,0,0);
511 return true;
512 }
513 return false;
514 }
515
516 void HapticManager::dispose(void){
517 hd->releaseDevice();
518 delete hd;
519 }
520
521
522 /* static constants initialization */
523 const int HapticManager::NO_ID = 0;
524
525 const int HapticManager::GRAPHIC_SCALE = 2;
526 const int HapticManager::SPRING_FORCE_FACTOR = 200;
527 const int HapticManager::LINE_FORCE_FACTOR_STICKY = 2000;
528 const int HapticManager::LINE_FORCE_FACTOR_LOOSE = 100;
529 const int HapticManager::POINT_FORCE_FACTOR = 20;
530 const double HapticManager::POINT_COLLISION_THRESHOLD = 0.0025;
531 const double HapticManager::LINE_COLLISION_THRESHOLD = 0.0025;
532 const double HapticManager::DRAGGING_SOUND_THRESHOLD = 0.01;
533
534 const int HapticManager::LOOSE_MODE_SOUND = 0;
535 const int HapticManager::STICKY_MODE_SOUND = 1;
536 const int HapticManager::DRAGGING_SOUND = 3;
537
538 const char HapticManager::MOVE_CMD = 'm';
539 const char HapticManager::PLAY_SOUND_CMD = 'g';
540 const char HapticManager::SPEAK_NAME_CMD = 't';
541 const char HapticManager::PICKUP_CMD = 'c';
542 const char HapticManager::SPEAK_INFO_CMD = 'i';
543 const char HapticManager::SELECT_CMD = 's';
544 const char HapticManager::UNSELECT_CMD = 'u';
545
546 const HAPIInt32 HapticManager::FRONT_BUTTON = (1 << 2);
547 const HAPIInt32 HapticManager::LEFT_BUTTON = (1 << 1);
548 const HAPIInt32 HapticManager::RIGHT_BUTTON = (1 << 3);
549 const HAPIInt32 HapticManager::REAR_BUTTON = (1 << 0);
550 const HAPIInt32 HapticManager::NO_BUTTON = 0;