RotateWidget.cpp 12 KB

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