RotateWidget.h 14 KB

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