RotateWidget.h 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. // This file is part of libigl, a simple c++ geometry processing library.
  2. //
  3. // Copyright (C) 2013 Alec Jacobson <alecjacobson@gmail.com>
  4. //
  5. // This Source Code Form is subject to the terms of the Mozilla Public License
  6. // v. 2.0. If a copy of the MPL was not distributed with this file, You can
  7. // obtain one at http://mozilla.org/MPL/2.0/.
  8. #ifndef IGL_ROTATE_WIDGET_H
  9. #define IGL_ROTATE_WIDGET_H
  10. #include <Eigen/Geometry>
  11. #include <Eigen/Core>
  12. #include <vector>
  13. #include <igl/material_colors.h>
  14. namespace igl
  15. {
  16. // 3D Rotate tool widget similar to Maya's. Works best if field of view angle
  17. // is less than ~25.
  18. class RotateWidget
  19. {
  20. public:
  21. inline static Eigen::Quaterniond axis_q(const int a);
  22. inline static Eigen::Vector3d view_direction(const int x, const int y);
  23. inline static Eigen::Vector3d view_direction(const Eigen::Vector3d & pos);
  24. Eigen::Vector3d pos;
  25. Eigen::Quaterniond rot,down_rot;
  26. // This line causes trouble if RotateWidget.h is included before bbw.h
  27. Eigen::Vector2d down_xy,drag_xy,down_dir;
  28. Eigen::Vector3d udown,udrag;
  29. double outer_radius_on_screen;
  30. double outer_over_inner;
  31. enum DownType
  32. {
  33. DOWN_TYPE_X = 0,
  34. DOWN_TYPE_Y = 1,
  35. DOWN_TYPE_Z = 2,
  36. DOWN_TYPE_OUTLINE = 3,
  37. DOWN_TYPE_TRACKBALL = 4,
  38. DOWN_TYPE_NONE = 5,
  39. NUM_DOWN_TYPES = 6
  40. } down_type, selected_type;
  41. inline RotateWidget();
  42. // Vector from origin to mouse click "Unprojected" onto plane with depth of
  43. // origin and scale to so that outer radius is 1
  44. //
  45. // Inputs:
  46. // x mouse x position
  47. // y mouse y position
  48. // Returns vector
  49. inline Eigen::Vector3d unproject_onto(const int x, const int y) const;
  50. // Shoot ray from mouse click to sphere
  51. //
  52. // Inputs:
  53. // x mouse x position
  54. // y mouse y position
  55. // Outputs:
  56. // hit position of hit
  57. // Returns true only if there was a hit
  58. inline bool intersect(
  59. const int x,
  60. const int y,
  61. Eigen::Vector3d & hit) const;
  62. inline double unprojected_inner_radius() const;
  63. inline bool down(const int x, const int y);
  64. inline bool drag(const int x, const int y);
  65. inline bool up(const int x, const int y);
  66. inline bool is_down() const;
  67. inline void draw() const;
  68. inline void draw_guide() const;
  69. };
  70. }
  71. // Implementation
  72. #include <igl/OpenGL_convenience.h>
  73. #include <igl/PI.h>
  74. #include <igl/EPS.h>
  75. #include <igl/ray_sphere_intersect.h>
  76. #include <igl/project.h>
  77. #include <igl/mat_to_quat.h>
  78. #include <igl/trackball.h>
  79. #include <igl/unproject.h>
  80. #include <iostream>
  81. #include <cassert>
  82. inline Eigen::Quaterniond igl::RotateWidget::axis_q(const int a)
  83. {
  84. assert(a<3 && a>=0);
  85. const Eigen::Quaterniond axes[3] = {
  86. Eigen::Quaterniond(Eigen::AngleAxisd(igl::PI*0.5,Eigen::Vector3d(0,1,0))),
  87. Eigen::Quaterniond(Eigen::AngleAxisd(igl::PI*0.5,Eigen::Vector3d(1,0,0))),
  88. Eigen::Quaterniond::Identity()};
  89. return axes[a];
  90. }
  91. inline Eigen::Vector3d igl::RotateWidget::view_direction(const int x, const int y)
  92. {
  93. using namespace Eigen;
  94. using namespace igl;
  95. const Vector3d win_s(x,y,0), win_d(x,y,1);
  96. const Vector3d s = unproject(win_s);
  97. const Vector3d d = unproject(win_d);
  98. return d-s;
  99. }
  100. inline Eigen::Vector3d igl::RotateWidget::view_direction(const Eigen::Vector3d & pos)
  101. {
  102. using namespace Eigen;
  103. using namespace igl;
  104. const Vector3d ppos = project(pos);
  105. return view_direction(ppos(0),ppos(1));
  106. }
  107. inline igl::RotateWidget::RotateWidget():
  108. pos(0,0,0),
  109. rot(Eigen::Quaterniond::Identity()),
  110. down_rot(rot),
  111. down_xy(-1,-1),drag_xy(-1,-1),
  112. outer_radius_on_screen(91.),
  113. outer_over_inner(1.13684210526),
  114. down_type(DOWN_TYPE_NONE),
  115. selected_type(DOWN_TYPE_NONE)
  116. {
  117. }
  118. inline Eigen::Vector3d igl::RotateWidget::unproject_onto(
  119. const int x,
  120. const int y) const
  121. {
  122. using namespace Eigen;
  123. using namespace igl;
  124. // KNOWN BUG: This projects to same depths as pos. I think what we actually
  125. // want is The intersection with the plane perpendicular to the view
  126. // direction at pos. If the field of view angle is small then this difference
  127. // is negligible.
  128. //const Vector3d ppos = project(pos);
  129. //const Vector3d uxy = unproject( Vector3d(x,y,ppos(2)));
  130. // http://en.wikipedia.org/wiki/Line-plane_intersection
  131. //
  132. // Hrrmmm. There's still something wrong here if the ball's in the corner of
  133. // the screen. Am I somehow not accounting for perspective correctly?
  134. //
  135. // Q: What about just projecting the circle's equation and solving for the
  136. // distance?
  137. const Vector3d l0 = unproject(Vector3d(x,y,0));
  138. const Vector3d l = unproject(Vector3d(x,y,1))-l0;
  139. const Vector3d n = view_direction(pos);
  140. const double t = (pos-l0).dot(n)/l.dot(n);
  141. const Vector3d uxy = l0+t*l;
  142. return (uxy-pos)/unprojected_inner_radius()*outer_over_inner*outer_over_inner;
  143. }
  144. inline bool igl::RotateWidget::intersect(
  145. const int x,
  146. const int y,
  147. Eigen::Vector3d & hit) const
  148. {
  149. using namespace Eigen;
  150. using namespace igl;
  151. Vector3d view = view_direction(x,y);
  152. const Vector3d ppos = project(pos);
  153. Vector3d uxy = unproject(Vector3d(x,y,ppos(2)));
  154. double t0,t1;
  155. if(!ray_sphere_intersect(uxy,view,pos,unprojected_inner_radius(),t0,t1))
  156. {
  157. return false;
  158. }
  159. hit = uxy+t0*view;
  160. return true;
  161. }
  162. inline double igl::RotateWidget::unprojected_inner_radius() const
  163. {
  164. using namespace Eigen;
  165. using namespace igl;
  166. Vector3d off,ppos,ppos_off,pos_off;
  167. project(pos,ppos);
  168. ppos_off = ppos;
  169. ppos_off(0) += outer_radius_on_screen/outer_over_inner;
  170. unproject(ppos_off,pos_off);
  171. return (pos-pos_off).norm();
  172. }
  173. inline bool igl::RotateWidget::down(const int x, const int y)
  174. {
  175. using namespace Eigen;
  176. using namespace igl;
  177. using namespace std;
  178. down_type = DOWN_TYPE_NONE;
  179. selected_type = DOWN_TYPE_NONE;
  180. down_xy = Vector2d(x,y);
  181. drag_xy = down_xy;
  182. down_rot = rot;
  183. Vector3d ppos = project(pos);
  184. const double r = (ppos.head(2) - down_xy).norm();
  185. const double thresh = 3;
  186. if(fabs(r - outer_radius_on_screen)<thresh)
  187. {
  188. udown = unproject_onto(x,y);
  189. udrag = udown;
  190. down_type = DOWN_TYPE_OUTLINE;
  191. selected_type = DOWN_TYPE_OUTLINE;
  192. // project mouse to same depth as pos
  193. return true;
  194. }else if(r < outer_radius_on_screen/outer_over_inner+thresh*0.5)
  195. {
  196. Vector3d hit;
  197. const bool is_hit = intersect(down_xy(0),down_xy(1),hit);
  198. if(!is_hit)
  199. {
  200. //cout<<"~~~!is_hit"<<endl;
  201. }
  202. auto on_meridian = [&](
  203. const Vector3d & hit,
  204. const Quaterniond & rot,
  205. const Quaterniond m,
  206. Vector3d & pl_hit) -> bool
  207. {
  208. // project onto rotate plane
  209. pl_hit = hit-pos;
  210. pl_hit = (m.conjugate()*rot.conjugate()*pl_hit).eval();
  211. pl_hit(2) = 0;
  212. pl_hit = (rot*m*pl_hit).eval();
  213. pl_hit.normalize();
  214. pl_hit *= unprojected_inner_radius();
  215. pl_hit += pos;
  216. return (project(pl_hit).head(2)-project(hit).head(2)).norm()<2*thresh;
  217. };
  218. udown = (hit-pos).normalized()/outer_radius_on_screen;
  219. udrag = udown;
  220. for(int a = 0;a<3;a++)
  221. {
  222. Vector3d pl_hit;
  223. if(on_meridian(hit,rot,Quaterniond(axis_q(a)),pl_hit))
  224. {
  225. udown = (pl_hit-pos).normalized()/outer_radius_on_screen;
  226. udrag = udown;
  227. down_type = DownType(DOWN_TYPE_X+a);
  228. selected_type = down_type;
  229. {
  230. Vector3d dir3 = axis_q(a).conjugate()*down_rot.conjugate()*(hit-pos);
  231. dir3 = AngleAxisd(-PI*0.5,Vector3d(0,0,1))*dir3;
  232. dir3 = (rot*axis_q(a)*dir3).eval();
  233. down_dir = (project((hit+dir3).eval())-project(hit)).head(2);
  234. down_dir.normalize();
  235. //// flip y because y coordinate is going to be given backwards in
  236. //// drag()
  237. //down_dir(1) *= -1;
  238. }
  239. return true;
  240. }
  241. }
  242. //assert(is_hit);
  243. down_type = DOWN_TYPE_TRACKBALL;
  244. selected_type = DOWN_TYPE_TRACKBALL;
  245. return true;
  246. }else
  247. {
  248. return false;
  249. }
  250. }
  251. inline bool igl::RotateWidget::drag(const int x, const int y)
  252. {
  253. using namespace igl;
  254. using namespace std;
  255. using namespace Eigen;
  256. drag_xy = Vector2d(x,y);
  257. switch(down_type)
  258. {
  259. case DOWN_TYPE_NONE:
  260. return false;
  261. default:
  262. {
  263. const Quaterniond & q = axis_q(down_type-DOWN_TYPE_X);
  264. const double dtheta = -(drag_xy - down_xy).dot(down_dir)/
  265. outer_radius_on_screen/outer_over_inner*PI/2.;
  266. Quaterniond dq(AngleAxisd(dtheta,down_rot*q*Vector3d(0,0,1)));
  267. rot = dq * down_rot;
  268. udrag = dq * udown;
  269. return true;
  270. }
  271. case DOWN_TYPE_OUTLINE:
  272. {
  273. Vector3d ppos = project(pos);
  274. // project mouse to same depth as pos
  275. udrag = unproject_onto(x,y);
  276. const Vector2d A = down_xy - ppos.head(2);
  277. const Vector2d B = drag_xy - ppos.head(2);
  278. const double dtheta = atan2(A(0)*B(1)-A(1)*B(0),A(0)*B(0)+A(1)*B(1));
  279. Vector3d n = view_direction(pos).normalized();
  280. Quaterniond dq(AngleAxisd(dtheta,-n));
  281. //Vector3d n = udrag.cross(udown).normalized();
  282. //Quaterniond dq(AngleAxisd(fabs(dtheta),-n));
  283. rot = dq * down_rot;
  284. }
  285. return true;
  286. case DOWN_TYPE_TRACKBALL:
  287. {
  288. Vector3d ppos = project(pos);
  289. const double r = (double)outer_radius_on_screen/outer_over_inner*2.0;
  290. //const int h = w;
  291. Vector4i vp;
  292. glGetIntegerv(GL_VIEWPORT,vp.data());
  293. const int h = vp(3);
  294. Quaterniond dq;
  295. trackball(
  296. r,r,
  297. 1,
  298. Quaterniond::Identity(),
  299. double( down_xy(0)-ppos(0) )+r/2.,
  300. double((h-down_xy(1))-(h-ppos(1)))+r/2.,
  301. double( x-ppos(0) )+r/2.,
  302. double( (h-y)-(h-ppos(1)))+r/2.,
  303. dq);
  304. // We've computed change in rotation according to this view:
  305. // R = mv * r, R' = rot * (mv * r)
  306. // But we only want new value for r:
  307. // R' = mv * r'
  308. // mv * r' = rot * (mv * r)
  309. // r' = mv* * rot * mv * r
  310. Matrix4d mv;
  311. glGetDoublev(GL_MODELVIEW_MATRIX,mv.data());
  312. Quaterniond scene_rot;
  313. // Convert modelview matrix to quaternion
  314. mat4_to_quat(mv.data(),scene_rot.coeffs().data());
  315. scene_rot.normalize();
  316. rot = scene_rot.conjugate() * dq * scene_rot * down_rot;
  317. }
  318. return true;
  319. }
  320. }
  321. inline bool igl::RotateWidget::up(const int /*x*/, const int /*y*/)
  322. {
  323. down_type = DOWN_TYPE_NONE;
  324. return false;
  325. }
  326. inline bool igl::RotateWidget::is_down() const
  327. {
  328. return down_type != DOWN_TYPE_NONE;
  329. }
  330. inline void igl::RotateWidget::draw() const
  331. {
  332. using namespace Eigen;
  333. using namespace std;
  334. using namespace igl;
  335. int l,dt;
  336. glGetIntegerv(GL_LIGHTING,&l);
  337. glGetIntegerv(GL_DEPTH_TEST,&dt);
  338. double lw;
  339. glGetDoublev(GL_LINE_WIDTH,&lw);
  340. glDisable(GL_LIGHTING);
  341. glDisable(GL_DEPTH_TEST);
  342. glLineWidth(2.0);
  343. double r = unprojected_inner_radius();
  344. Vector3d view = view_direction(pos).normalized();
  345. auto draw_circle = [&](const bool cull)
  346. {
  347. Vector3d view = view_direction(pos).normalized();
  348. glBegin(GL_LINES);
  349. const double th_step = (2.0*igl::PI/100.0);
  350. for(double th = 0;th<2.0*igl::PI+th_step;th+=th_step)
  351. {
  352. Vector3d a(cos(th),sin(th),0.0);
  353. Vector3d b(cos(th+th_step),sin(th+th_step),0.0);
  354. if(!cull || (0.5*(a+b)).dot(view)<FLOAT_EPS)
  355. {
  356. glVertex3dv(a.data());
  357. glVertex3dv(b.data());
  358. }
  359. }
  360. glEnd();
  361. };
  362. glPushMatrix();
  363. glTranslated(pos(0),pos(1),pos(2));
  364. glScaled(r,r,r);
  365. // Draw outlines
  366. {
  367. glPushMatrix();
  368. glColor4fv(MAYA_GREY.data());
  369. Quaterniond q;
  370. q.setFromTwoVectors(Vector3d(0,0,1),view);
  371. glMultMatrixd(Affine3d(q).matrix().data());
  372. draw_circle(false);
  373. glScaled(outer_over_inner,outer_over_inner,outer_over_inner);
  374. if(selected_type == DOWN_TYPE_OUTLINE)
  375. {
  376. glColor4fv(MAYA_YELLOW.data());
  377. }else
  378. {
  379. glColor4fv(MAYA_CYAN.data());
  380. }
  381. draw_circle(false);
  382. glPopMatrix();
  383. }
  384. // Draw quartiles
  385. {
  386. glPushMatrix();
  387. glMultMatrixd(Affine3d(rot).matrix().data());
  388. if(selected_type == DOWN_TYPE_Z)
  389. {
  390. glColor4fv(MAYA_YELLOW.data());
  391. }else
  392. {
  393. glColor4fv(MAYA_BLUE.data());
  394. }
  395. draw_circle(true);
  396. if(selected_type == DOWN_TYPE_Y)
  397. {
  398. glColor4fv(MAYA_YELLOW.data());
  399. }else
  400. {
  401. glColor4fv(MAYA_GREEN.data());
  402. }
  403. glRotated(90.0,1.0,0.0,0.0);
  404. draw_circle(true);
  405. if(selected_type == DOWN_TYPE_X)
  406. {
  407. glColor4fv(MAYA_YELLOW.data());
  408. }else
  409. {
  410. glColor4fv(MAYA_RED.data());
  411. }
  412. glRotated(90.0,0.0,1.0,0.0);
  413. draw_circle(true);
  414. glPopMatrix();
  415. }
  416. glColor4fv(MAYA_GREY.data());
  417. draw_guide();
  418. glPopMatrix();
  419. glLineWidth(lw);
  420. (l ? glEnable(GL_LIGHTING):glDisable(GL_LIGHTING));
  421. (dt ? glEnable(GL_DEPTH_TEST):glDisable(GL_DEPTH_TEST));
  422. };
  423. inline void igl::RotateWidget::draw_guide() const
  424. {
  425. using namespace Eigen;
  426. using namespace std;
  427. using namespace igl;
  428. int posm,post,cf;
  429. glGetIntegerv(GL_CULL_FACE,&cf);
  430. glGetIntegerv(GL_POINT_SMOOTH,&posm);
  431. glGetIntegerv(GL_POLYGON_STIPPLE,&post);
  432. double posi;
  433. glGetDoublev(GL_POINT_SIZE,&posi);
  434. // http://www.codeproject.com/Articles/23444/A-Simple-OpenGL-Stipple-Polygon-Example-EP_OpenGL_
  435. const GLubyte halftone[] = {
  436. 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
  437. 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
  438. 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
  439. 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
  440. 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
  441. 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
  442. 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
  443. 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
  444. 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
  445. 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
  446. 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
  447. 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
  448. 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
  449. 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
  450. 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
  451. 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55};
  452. switch(down_type)
  453. {
  454. case DOWN_TYPE_NONE:
  455. case DOWN_TYPE_TRACKBALL:
  456. return;
  457. case DOWN_TYPE_OUTLINE:
  458. glScaled(outer_over_inner,outer_over_inner,outer_over_inner);
  459. break;
  460. default:
  461. break;
  462. }
  463. const Vector3d nudown(udown.normalized()),
  464. nudrag(udrag.normalized());
  465. glPushMatrix();
  466. glDisable(GL_CULL_FACE);
  467. glDisable(GL_POINT_SMOOTH);
  468. glPointSize(5.);
  469. glBegin(GL_POINTS);
  470. glVertex3dv(nudown.data());
  471. glVertex3d(0,0,0);
  472. glVertex3dv(nudrag.data());
  473. glEnd();
  474. glBegin(GL_LINE_STRIP);
  475. glVertex3dv(nudown.data());
  476. glVertex3d(0,0,0);
  477. glVertex3dv(nudrag.data());
  478. glEnd();
  479. glEnable(GL_POLYGON_STIPPLE);
  480. glPolygonStipple(halftone);
  481. glBegin(GL_TRIANGLE_FAN);
  482. glVertex3d(0,0,0);
  483. Quaterniond dq = rot * down_rot.conjugate();
  484. //dq.setFromTwoVectors(nudown,nudrag);
  485. for(double t = 0;t<1;t+=0.1)
  486. {
  487. const Vector3d p = Quaterniond::Identity().slerp(t,dq) * nudown;
  488. glVertex3dv(p.data());
  489. }
  490. glVertex3dv(nudrag.data());
  491. glEnd();
  492. glPopMatrix();
  493. glPointSize(posi);
  494. (cf?glEnable(GL_CULL_FACE):glDisable(GL_CULL_FACE));
  495. (posm?glEnable(GL_POINT_SMOOTH):glDisable(GL_POINT_SMOOTH));
  496. (post?glEnable(GL_POLYGON_STIPPLE):glDisable(GL_POLYGON_STIPPLE));
  497. }
  498. #endif