example.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. // Small GLUT application to test different scene rotation paradigms
  2. //
  3. #include "trackball.h"
  4. #include <igl/two_axis_valuator_fixed_up.h>
  5. #include <igl/readOBJ.h>
  6. #include <igl/writeOBJ.h>
  7. #include <igl/writeOFF.h>
  8. #include <igl/readWRL.h>
  9. #include <igl/report_gl_error.h>
  10. #include <igl/triangulate.h>
  11. #include <igl/readOFF.h>
  12. #include <igl/readMESH.h>
  13. #include <igl/draw_mesh.h>
  14. #include <igl/draw_floor.h>
  15. #include <igl/pathinfo.h>
  16. #include <igl/list_to_matrix.h>
  17. #include <igl/quat_to_mat.h>
  18. #include <igl/per_face_normals.h>
  19. #include <igl/material_colors.h>
  20. #include <igl/trackball.h>
  21. #include <igl/snap_to_canonical_view_quat.h>
  22. #include <igl/snap_to_fixed_up.h>
  23. #include <igl/REDRUM.h>
  24. #include <igl/Camera.h>
  25. #include <igl/ReAntTweakBar.h>
  26. #include <igl/get_seconds.h>
  27. #include <Eigen/Core>
  28. #include <Eigen/Geometry>
  29. #include <GLUT/glut.h>
  30. #include <Carbon/Carbon.h>
  31. #include <string>
  32. #include <vector>
  33. #include <stack>
  34. #include <iostream>
  35. Eigen::MatrixXd V,N;
  36. Eigen::VectorXd Vmid,Vmin,Vmax;
  37. double bbd = 1.0;
  38. Eigen::MatrixXi F;
  39. struct State
  40. {
  41. igl::Camera camera;
  42. } s;
  43. // See README for descriptions
  44. enum RotationType
  45. {
  46. ROTATION_TYPE_IGL_TRACKBALL = 0,
  47. ROTATION_TYPE_BELL_TRACKBALL = 1,
  48. ROTATION_TYPE_TWO_AXIS_VALUATOR = 2,
  49. ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP = 3,
  50. NUM_ROTATION_TYPES = 4,
  51. } rotation_type;
  52. enum CenterType
  53. {
  54. CENTER_TYPE_ORBIT = 0,
  55. CENTER_TYPE_FPS = 1,
  56. NUM_CENTER_TYPES = 2,
  57. } center_type = CENTER_TYPE_ORBIT;
  58. std::stack<State> undo_stack;
  59. std::stack<State> redo_stack;
  60. bool is_rotating = false;
  61. int down_x,down_y;
  62. igl::Camera down_camera;
  63. bool is_animating = false;
  64. double animation_start_time = 0;
  65. double ANIMATION_DURATION = 0.5;
  66. Eigen::Quaterniond animation_from_quat;
  67. Eigen::Quaterniond animation_to_quat;
  68. int width,height;
  69. Eigen::Vector4f light_pos(-0.1,-0.1,0.9,0);
  70. #define REBAR_NAME "temp.rbr"
  71. igl::ReTwBar rebar;
  72. void push_undo()
  73. {
  74. undo_stack.push(s);
  75. // Clear
  76. redo_stack = std::stack<State>();
  77. }
  78. // No-op setter, does nothing
  79. void TW_CALL no_op(const void * /*value*/, void * /*clientData*/)
  80. {
  81. }
  82. void TW_CALL set_rotation_type(const void * value, void * clientData)
  83. {
  84. using namespace Eigen;
  85. using namespace std;
  86. using namespace igl;
  87. const RotationType old_rotation_type = rotation_type;
  88. rotation_type = *(const RotationType *)(value);
  89. if(rotation_type == ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP &&
  90. old_rotation_type != ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP)
  91. {
  92. push_undo();
  93. animation_from_quat = s.camera.m_rotation_conj;
  94. snap_to_fixed_up(animation_from_quat,animation_to_quat);
  95. // start animation
  96. animation_start_time = get_seconds();
  97. is_animating = true;
  98. }
  99. }
  100. void TW_CALL get_rotation_type(void * value, void *clientData)
  101. {
  102. RotationType * rt = (RotationType *)(value);
  103. *rt = rotation_type;
  104. }
  105. void reshape(int width, int height)
  106. {
  107. ::width = width;
  108. ::height = height;
  109. glViewport(0,0,width,height);
  110. // Send the new window size to AntTweakBar
  111. TwWindowSize(width, height);
  112. s.camera.m_aspect = (double)width/(double)height;
  113. }
  114. void push_scene()
  115. {
  116. using namespace igl;
  117. using namespace std;
  118. glMatrixMode(GL_PROJECTION);
  119. glPushMatrix();
  120. glLoadIdentity();
  121. auto & camera = s.camera;
  122. gluPerspective(camera.m_angle,camera.m_aspect,camera.m_near,camera.m_far);
  123. glMatrixMode(GL_MODELVIEW);
  124. glPushMatrix();
  125. glLoadIdentity();
  126. gluLookAt(
  127. camera.eye()(0), camera.eye()(1), camera.eye()(2),
  128. camera.at()(0), camera.at()(1), camera.at()(2),
  129. camera.up()(0), camera.up()(1), camera.up()(2));
  130. }
  131. void push_object()
  132. {
  133. using namespace igl;
  134. glPushMatrix();
  135. glScaled(2./bbd,2./bbd,2./bbd);
  136. glTranslated(-Vmid(0),-Vmid(1),-Vmid(2));
  137. }
  138. void pop_object()
  139. {
  140. glPopMatrix();
  141. }
  142. void pop_scene()
  143. {
  144. glMatrixMode(GL_PROJECTION);
  145. glPopMatrix();
  146. glMatrixMode(GL_MODELVIEW);
  147. glPopMatrix();
  148. }
  149. // Set up double-sided lights
  150. void lights()
  151. {
  152. using namespace std;
  153. using namespace Eigen;
  154. glEnable(GL_LIGHTING);
  155. glLightModelf(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
  156. glEnable(GL_LIGHT0);
  157. glEnable(GL_LIGHT1);
  158. float WHITE[4] = {0.8,0.8,0.8,1.};
  159. float GREY[4] = {0.4,0.4,0.4,1.};
  160. float BLACK[4] = {0.,0.,0.,1.};
  161. Vector4f pos = light_pos;
  162. glLightfv(GL_LIGHT0,GL_AMBIENT,GREY);
  163. glLightfv(GL_LIGHT0,GL_DIFFUSE,WHITE);
  164. glLightfv(GL_LIGHT0,GL_SPECULAR,BLACK);
  165. glLightfv(GL_LIGHT0,GL_POSITION,pos.data());
  166. pos(0) *= -1;
  167. pos(1) *= -1;
  168. pos(2) *= -1;
  169. glLightfv(GL_LIGHT1,GL_AMBIENT,GREY);
  170. glLightfv(GL_LIGHT1,GL_DIFFUSE,WHITE);
  171. glLightfv(GL_LIGHT1,GL_SPECULAR,BLACK);
  172. glLightfv(GL_LIGHT1,GL_POSITION,pos.data());
  173. }
  174. void display()
  175. {
  176. using namespace igl;
  177. using namespace std;
  178. using namespace Eigen;
  179. glClearColor(1,1,1,0);
  180. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  181. if(is_animating)
  182. {
  183. double t = (get_seconds() - animation_start_time)/ANIMATION_DURATION;
  184. if(t > 1)
  185. {
  186. t = 1;
  187. is_animating = false;
  188. }
  189. Quaterniond q = animation_from_quat.slerp(t,animation_to_quat).normalized();
  190. auto & camera = s.camera;
  191. switch(center_type)
  192. {
  193. default:
  194. case CENTER_TYPE_ORBIT:
  195. camera.orbit(q.conjugate());
  196. break;
  197. case CENTER_TYPE_FPS:
  198. camera.turn_eye(q.conjugate());
  199. break;
  200. }
  201. }
  202. glEnable(GL_DEPTH_TEST);
  203. glEnable(GL_NORMALIZE);
  204. lights();
  205. push_scene();
  206. push_object();
  207. // Set material properties
  208. glDisable(GL_COLOR_MATERIAL);
  209. glMaterialfv(GL_FRONT, GL_AMBIENT, GOLD_AMBIENT);
  210. glMaterialfv(GL_FRONT, GL_DIFFUSE, GOLD_DIFFUSE );
  211. glMaterialfv(GL_FRONT, GL_SPECULAR, GOLD_SPECULAR);
  212. glMaterialf (GL_FRONT, GL_SHININESS, 128);
  213. glMaterialfv(GL_BACK, GL_AMBIENT, SILVER_AMBIENT);
  214. glMaterialfv(GL_BACK, GL_DIFFUSE, FAST_GREEN_DIFFUSE );
  215. glMaterialfv(GL_BACK, GL_SPECULAR, SILVER_SPECULAR);
  216. glMaterialf (GL_BACK, GL_SHININESS, 128);
  217. draw_mesh(V,F,N);
  218. pop_object();
  219. // Draw a nice floor
  220. glPushMatrix();
  221. const double floor_offset =
  222. -2./bbd*(V.col(1).maxCoeff()-Vmid(1));
  223. glTranslated(0,floor_offset,0);
  224. const float GREY[4] = {0.5,0.5,0.6,1.0};
  225. const float DARK_GREY[4] = {0.2,0.2,0.3,1.0};
  226. draw_floor(GREY,DARK_GREY);
  227. glPopMatrix();
  228. pop_scene();
  229. report_gl_error();
  230. TwDraw();
  231. glutSwapBuffers();
  232. glutPostRedisplay();
  233. }
  234. void mouse_wheel(int wheel, int direction, int mouse_x, int mouse_y)
  235. {
  236. using namespace std;
  237. using namespace igl;
  238. using namespace Eigen;
  239. GLint viewport[4];
  240. glGetIntegerv(GL_VIEWPORT,viewport);
  241. if(wheel == 0 && TwMouseMotion(mouse_x, viewport[3] - mouse_y))
  242. {
  243. static double mouse_scroll_y = 0;
  244. const double delta_y = 0.125*direction;
  245. mouse_scroll_y += delta_y;
  246. TwMouseWheel(mouse_scroll_y);
  247. return;
  248. }
  249. push_undo();
  250. auto & camera = s.camera;
  251. switch(center_type)
  252. {
  253. case CENTER_TYPE_ORBIT:
  254. if(wheel==0)
  255. {
  256. // factor of zoom change
  257. double s = (1.-0.01*direction);
  258. //// FOV zoom: just widen angle. This is hardly ever appropriate.
  259. //camera.m_angle *= s;
  260. //camera.m_angle = min(max(camera.m_angle,1),89);
  261. camera.push_away(s);
  262. }else
  263. {
  264. // Dolly zoom:
  265. camera.dolly_zoom((double)direction*1.0);
  266. }
  267. break;
  268. default:
  269. case CENTER_TYPE_FPS:
  270. // Move `eye` and `at`
  271. camera.dolly((wheel==0?Vector3d(0,0,1):Vector3d(-1,0,0))*0.1*direction);
  272. break;
  273. }
  274. }
  275. void mouse(int glutButton, int glutState, int mouse_x, int mouse_y)
  276. {
  277. using namespace std;
  278. using namespace Eigen;
  279. using namespace igl;
  280. bool tw_using = TwEventMouseButtonGLUT(glutButton,glutState,mouse_x,mouse_y);
  281. switch(glutButton)
  282. {
  283. case GLUT_RIGHT_BUTTON:
  284. case GLUT_LEFT_BUTTON:
  285. {
  286. switch(glutState)
  287. {
  288. case 1:
  289. // up
  290. glutSetCursor(GLUT_CURSOR_INHERIT);
  291. is_rotating = false;
  292. break;
  293. case 0:
  294. if(!tw_using)
  295. {
  296. push_undo();
  297. glutSetCursor(GLUT_CURSOR_CYCLE);
  298. // collect information for trackball
  299. is_rotating = true;
  300. down_camera = s.camera;
  301. down_x = mouse_x;
  302. down_y = mouse_y;
  303. }
  304. break;
  305. }
  306. break;
  307. }
  308. // Scroll down
  309. case 3:
  310. {
  311. mouse_wheel(0,-1,mouse_x,mouse_y);
  312. break;
  313. }
  314. // Scroll up
  315. case 4:
  316. {
  317. mouse_wheel(0,1,mouse_x,mouse_y);
  318. break;
  319. }
  320. // Scroll left
  321. case 5:
  322. {
  323. mouse_wheel(1,-1,mouse_x,mouse_y);
  324. break;
  325. }
  326. // Scroll right
  327. case 6:
  328. {
  329. mouse_wheel(1,1,mouse_x,mouse_y);
  330. break;
  331. }
  332. }
  333. }
  334. void mouse_drag(int mouse_x, int mouse_y)
  335. {
  336. using namespace igl;
  337. using namespace std;
  338. using namespace Eigen;
  339. if(is_rotating)
  340. {
  341. glutSetCursor(GLUT_CURSOR_CYCLE);
  342. Quaterniond q;
  343. auto & camera = s.camera;
  344. switch(rotation_type)
  345. {
  346. case ROTATION_TYPE_IGL_TRACKBALL:
  347. {
  348. // Rotate according to trackball
  349. igl::trackball<double>(
  350. width,
  351. height,
  352. 2.0,
  353. down_camera.m_rotation_conj.coeffs().data(),
  354. down_x,
  355. down_y,
  356. mouse_x,
  357. mouse_y,
  358. q.coeffs().data());
  359. break;
  360. }
  361. case ROTATION_TYPE_BELL_TRACKBALL:
  362. {
  363. float down_quaternion[4];
  364. copy(
  365. down_camera.m_rotation_conj.coeffs().data(),
  366. down_camera.m_rotation_conj.coeffs().data()+4,
  367. down_quaternion);
  368. float new_quaternion[4];
  369. const float center_x = ((float)width)/2.0;
  370. const float center_y = ((float)height)/2.0;
  371. const double speed = 2.0f;
  372. const float half_width = ((float)width)/speed;
  373. const float half_height = ((float)height)/speed;
  374. ::trackball(new_quaternion,
  375. (float)(center_x-down_x)/half_width,
  376. (float)(down_y-center_y)/half_height,
  377. (float)(center_x-mouse_x)/half_width,
  378. (float)(mouse_y-center_y)/half_height);
  379. // I think we need to do this because we have z pointing out of the
  380. // screen rather than into the screen
  381. new_quaternion[2] = -new_quaternion[2];
  382. float float_quat[4];
  383. add_quats(down_quaternion,new_quaternion,float_quat);
  384. copy(float_quat,float_quat+4,q.coeffs().data());
  385. break;
  386. }
  387. case ROTATION_TYPE_TWO_AXIS_VALUATOR:
  388. {
  389. Quaterniond down_q = camera.m_rotation_conj;
  390. Vector3d axis(mouse_y-down_y,mouse_x-down_x,0);
  391. const double speed = 2.0;
  392. if(axis.norm() != 0)
  393. {
  394. q =
  395. Quaterniond(
  396. AngleAxisd(
  397. M_PI*axis.norm()/(double)width*speed/2.0,
  398. axis.normalized())) * down_q;
  399. q.normalize();
  400. }
  401. break;
  402. }
  403. case ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP:
  404. {
  405. // Rotate according to two axis valuator with fixed up vector
  406. two_axis_valuator_fixed_up(
  407. width, height,
  408. 2.0,
  409. down_camera.m_rotation_conj,
  410. down_x, down_y, mouse_x, mouse_y,
  411. q);
  412. break;
  413. }
  414. default:
  415. break;
  416. }
  417. switch(center_type)
  418. {
  419. default:
  420. case CENTER_TYPE_ORBIT:
  421. camera.orbit(q.conjugate());
  422. break;
  423. case CENTER_TYPE_FPS:
  424. camera.turn_eye(q.conjugate());
  425. break;
  426. }
  427. }
  428. }
  429. void init_relative()
  430. {
  431. using namespace Eigen;
  432. using namespace igl;
  433. per_face_normals(V,F,N);
  434. Vmax = V.colwise().maxCoeff();
  435. Vmin = V.colwise().minCoeff();
  436. Vmid = 0.5*(Vmax + Vmin);
  437. bbd = (Vmax-Vmin).norm();
  438. }
  439. KeyMap keyStates ;
  440. bool IS_KEYDOWN( uint16_t vKey )
  441. {
  442. uint8_t index = vKey / 32 ;
  443. uint8_t shift = vKey % 32 ;
  444. return keyStates[index].bigEndianValue & (1 << shift) ;
  445. }
  446. void redo()
  447. {
  448. using namespace std;
  449. if(!redo_stack.empty())
  450. {
  451. undo_stack.push(s);
  452. s = redo_stack.top();
  453. redo_stack.pop();
  454. }
  455. }
  456. void key(unsigned char key, int mouse_x, int mouse_y)
  457. {
  458. using namespace std;
  459. using namespace igl;
  460. using namespace Eigen;
  461. GetKeys(keyStates);
  462. const bool command_down = IS_KEYDOWN(kVK_Command);
  463. const bool shift_down = IS_KEYDOWN(kVK_Shift);
  464. switch(key)
  465. {
  466. // ESC
  467. case char(27):
  468. rebar.save(REBAR_NAME);
  469. // ^C
  470. case char(3):
  471. exit(0);
  472. case 'z':
  473. case 'Z':
  474. if(command_down)
  475. {
  476. if(shift_down)
  477. {
  478. redo();
  479. }else
  480. {
  481. undo();
  482. }
  483. break;
  484. }else
  485. {
  486. push_undo();
  487. Quaterniond q;
  488. snap_to_canonical_view_quat(s.camera.m_rotation_conj,1.0,q);
  489. switch(center_type)
  490. {
  491. default:
  492. case CENTER_TYPE_ORBIT:
  493. s.camera.orbit(q.conjugate());
  494. break;
  495. case CENTER_TYPE_FPS:
  496. s.camera.turn_eye(q.conjugate());
  497. break;
  498. }
  499. break;
  500. }
  501. default:
  502. if(!TwEventKeyboardGLUT(key,mouse_x,mouse_y))
  503. {
  504. cout<<"Unknown key command: "<<key<<" "<<int(key)<<endl;
  505. }
  506. }
  507. }
  508. int main(int argc, char * argv[])
  509. {
  510. using namespace std;
  511. using namespace Eigen;
  512. using namespace igl;
  513. string filename = "../shared/cheburashka.off";
  514. if(argc < 2)
  515. {
  516. cerr<<"Usage:"<<endl<<" ./example input.obj"<<endl;
  517. cout<<endl<<"Opening default mesh..."<<endl;
  518. }else
  519. {
  520. // Read and prepare mesh
  521. filename = argv[1];
  522. }
  523. // print key commands
  524. cout<<"[Click] and [drag] Rotate model using trackball."<<endl;
  525. cout<<"[Z,z] Snap rotation to canonical view."<<endl;
  526. cout<<"[⌘ Z] Undo."<<endl;
  527. cout<<"[⇧ ⌘ Z] Redo."<<endl;
  528. cout<<"[^C,ESC] Exit."<<endl;
  529. // dirname, basename, extension and filename
  530. string d,b,ext,f;
  531. pathinfo(filename,d,b,ext,f);
  532. // Convert extension to lower case
  533. transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
  534. vector<vector<double > > vV,vN,vTC;
  535. vector<vector<int > > vF,vFTC,vFN;
  536. if(ext == "obj")
  537. {
  538. // Convert extension to lower case
  539. if(!igl::readOBJ(filename,vV,vTC,vN,vF,vFTC,vFN))
  540. {
  541. return 1;
  542. }
  543. }else if(ext == "off")
  544. {
  545. // Convert extension to lower case
  546. if(!igl::readOFF(filename,vV,vF,vN))
  547. {
  548. return 1;
  549. }
  550. }else if(ext == "wrl")
  551. {
  552. // Convert extension to lower case
  553. if(!igl::readWRL(filename,vV,vF))
  554. {
  555. return 1;
  556. }
  557. //}else
  558. //{
  559. // // Convert extension to lower case
  560. // MatrixXi T;
  561. // if(!igl::readMESH(filename,V,T,F))
  562. // {
  563. // return 1;
  564. // }
  565. // //if(F.size() > T.size() || F.size() == 0)
  566. // {
  567. // boundary_faces(T,F);
  568. // }
  569. }
  570. if(vV.size() > 0)
  571. {
  572. if(!list_to_matrix(vV,V))
  573. {
  574. return 1;
  575. }
  576. triangulate(vF,F);
  577. }
  578. init_relative();
  579. // Init glut
  580. glutInit(&argc,argv);
  581. if( !TwInit(TW_OPENGL, NULL) )
  582. {
  583. // A fatal error occured
  584. fprintf(stderr, "AntTweakBar initialization failed: %s\n", TwGetLastError());
  585. return 1;
  586. }
  587. // Create a tweak bar
  588. rebar.TwNewBar("TweakBar");
  589. rebar.TwAddVarRW("camera_rotation", TW_TYPE_QUAT4D,
  590. s.camera.m_rotation_conj.coeffs().data(), "open readonly=true");
  591. TwType RotationTypeTW = ReTwDefineEnumFromString("RotationType",
  592. "igl_trackball,bell_trackball,two-axis-valuator,two-a...-fixed-up");
  593. rebar.TwAddVarCB( "rotation_type", RotationTypeTW,
  594. set_rotation_type,get_rotation_type,NULL,"keyIncr=] keyDecr=[");
  595. TwType CenterTypeTW = ReTwDefineEnumFromString("CenterType","orbit,fps");
  596. rebar.TwAddVarRW("center_type", CenterTypeTW,&center_type,
  597. "keyIncr={ keyDecr=}");
  598. rebar.load(REBAR_NAME);
  599. // Init antweakbar
  600. glutInitDisplayString( "rgba depth double samples>=8 ");
  601. glutInitWindowSize(glutGet(GLUT_SCREEN_WIDTH)/2.0,glutGet(GLUT_SCREEN_HEIGHT)/2.0);
  602. glutCreateWindow("upright");
  603. glutDisplayFunc(display);
  604. glutReshapeFunc(reshape);
  605. glutKeyboardFunc(key);
  606. glutMouseFunc(mouse);
  607. glutMotionFunc(mouse_drag);
  608. glutPassiveMotionFunc((GLUTmousemotionfun)TwEventMouseMotionGLUT);
  609. glutMainLoop();
  610. return 0;
  611. }