RotateWidget.h 16 KB

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