#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __APPLE__ #include #else #include #endif #ifndef GLUT_WHEEL_UP #define GLUT_WHEEL_UP 3 #endif #ifndef GLUT_WHEEL_DOWN #define GLUT_WHEEL_DOWN 4 #endif #ifndef GLUT_WHEEL_RIGHT #define GLUT_WHEEL_RIGHT 5 #endif #ifndef GLUT_WHEEL_LEFT #define GLUT_WHEEL_LEFT 6 #endif #ifndef GLUT_ACTIVE_COMMAND #define GLUT_ACTIVE_COMMAND 8 #endif #include #include #include #include #include enum SkelStyleType { SKEL_STYLE_TYPE_3D = 0, SKEL_STYLE_TYPE_VECTOR_GRAPHICS = 1, NUM_SKEL_STYLE_TYPE = 2 }skel_style; Eigen::MatrixXd V,N,sorted_N; Eigen::Vector3d Vmid,Vcen; double bbd = 1.0; Eigen::MatrixXi F,sorted_F; Eigen::VectorXi P; igl::Camera camera; struct State { Eigen::MatrixXd C; Eigen::MatrixXi BE; Eigen::VectorXi sel; } s; bool wireframe = false; bool skeleton_on_top = false; double alpha = 0.5; // See README for descriptions enum RotationType { ROTATION_TYPE_IGL_TRACKBALL = 0, ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP = 1, NUM_ROTATION_TYPES = 2, } rotation_type = ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP; std::stack undo_stack; std::stack redo_stack; bool is_rotating = false; bool is_dragging = false; bool new_leaf_on_drag = false; bool new_root_on_drag = false; int down_x,down_y; Eigen::MatrixXd down_C; igl::Camera down_camera; std::string output_filename; bool is_animating = false; double animation_start_time = 0; double ANIMATION_DURATION = 0.5; Eigen::Quaterniond animation_from_quat; Eigen::Quaterniond animation_to_quat; int width,height; Eigen::Vector4f light_pos(-0.1,-0.1,0.9,0); #define REBAR_NAME "temp.rbr" igl::anttweakbar::ReTwBar rebar; igl::embree::EmbreeIntersector ei; void push_undo() { undo_stack.push(s); // Clear redo_stack = std::stack(); } // No-op setter, does nothing void TW_CALL no_op(const void * /*value*/, void * /*clientData*/) { } void TW_CALL set_rotation_type(const void * value, void * clientData) { using namespace Eigen; using namespace std; using namespace igl; const RotationType old_rotation_type = rotation_type; rotation_type = *(const RotationType *)(value); if(rotation_type == ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP && old_rotation_type != ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP) { push_undo(); animation_from_quat = camera.m_rotation_conj; snap_to_fixed_up(animation_from_quat,animation_to_quat); // start animation animation_start_time = get_seconds(); is_animating = true; } } void TW_CALL get_rotation_type(void * value, void *clientData) { RotationType * rt = (RotationType *)(value); *rt = rotation_type; } void reshape(int width, int height) { ::width = width; ::height = height; glViewport(0,0,width,height); // Send the new window size to AntTweakBar TwWindowSize(width, height); camera.m_aspect = (double)width/(double)height; } void push_scene() { using namespace igl; using namespace std; glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluPerspective(camera.m_angle,camera.m_aspect,camera.m_near,camera.m_far); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); gluLookAt( camera.eye()(0), camera.eye()(1), camera.eye()(2), camera.at()(0), camera.at()(1), camera.at()(2), camera.up()(0), camera.up()(1), camera.up()(2)); } void push_object() { using namespace igl; glPushMatrix(); glScaled(2./bbd,2./bbd,2./bbd); glTranslated(-Vmid(0),-Vmid(1),-Vmid(2)); } void pop_object() { glPopMatrix(); } void pop_scene() { glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); } // Set up double-sided lights void lights() { using namespace std; using namespace Eigen; glEnable(GL_LIGHTING); glLightModelf(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE); glEnable(GL_LIGHT0); glEnable(GL_LIGHT1); float WHITE[4] = {0.8,0.8,0.8,1.}; float GREY[4] = {0.4,0.4,0.4,1.}; float BLACK[4] = {0.,0.,0.,1.}; Vector4f pos = light_pos; glLightfv(GL_LIGHT0,GL_AMBIENT,GREY); glLightfv(GL_LIGHT0,GL_DIFFUSE,WHITE); glLightfv(GL_LIGHT0,GL_SPECULAR,BLACK); glLightfv(GL_LIGHT0,GL_POSITION,pos.data()); pos(0) *= -1; pos(1) *= -1; pos(2) *= -1; glLightfv(GL_LIGHT1,GL_AMBIENT,GREY); glLightfv(GL_LIGHT1,GL_DIFFUSE,WHITE); glLightfv(GL_LIGHT1,GL_SPECULAR,BLACK); glLightfv(GL_LIGHT1,GL_POSITION,pos.data()); } void sort() { using namespace std; using namespace Eigen; using namespace igl; push_scene(); push_object(); VectorXi I; igl::opengl2::sort_triangles(V,F,sorted_F,I); slice(N,I,1,sorted_N); pop_object(); pop_scene(); } void display() { using namespace igl; using namespace std; using namespace Eigen; const float back[4] = {0.75, 0.75, 0.75,0}; glClearColor(back[0],back[1],back[2],0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); static bool first = true; if(first) { sort(); first = false; } if(is_animating) { double t = (get_seconds() - animation_start_time)/ANIMATION_DURATION; if(t > 1) { t = 1; is_animating = false; } Quaterniond q = animation_from_quat.slerp(t,animation_to_quat).normalized(); camera.orbit(q.conjugate()); } glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glEnable(GL_NORMALIZE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); lights(); push_scene(); // Draw a nice floor glEnable(GL_DEPTH_TEST); glPushMatrix(); const double floor_offset = -2./bbd*(V.col(1).maxCoeff()-Vmid(1)); glTranslated(0,floor_offset,0); const float GREY[4] = {0.5,0.5,0.6,1.0}; const float DARK_GREY[4] = {0.2,0.2,0.3,1.0}; glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); igl::opengl2::draw_floor(GREY,DARK_GREY); glDisable(GL_CULL_FACE); glPopMatrix(); push_object(); const auto & draw_skeleton = []() { switch(skel_style) { default: case SKEL_STYLE_TYPE_3D: { MatrixXf colors = MAYA_VIOLET.transpose().replicate(s.BE.rows(),1); for(int si=0;si & mask) { const int count = std::count(mask.begin(),mask.end(),true); Eigen::VectorXi sel(count); int s = 0; for(int c = 0;c<(int)mask.size();c++) { if(mask[c]) { sel(s) = c; s++; } } return sel; } std::vector selection_mask(const Eigen::VectorXi & sel, const int n) { std::vector mask(n,false); for(int si = 0;si old_mask = selection_mask(s.sel,s.C.rows()); vector mask(old_mask.size(),false); double min_dist = 1e25; bool sel_changed = false; bool found = false; for(int c = 0;c( width, height, 2.0, down_camera.m_rotation_conj.coeffs().data(), down_x, down_y, mouse_x, mouse_y, q.coeffs().data()); break; } case ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP: { // Rotate according to two axis valuator with fixed up vector two_axis_valuator_fixed_up( width, height, 2.0, down_camera.m_rotation_conj, down_x, down_y, mouse_x, mouse_y, q); break; } default: break; } camera.orbit(q.conjugate()); } if(is_dragging) { push_scene(); push_object(); if(new_leaf_on_drag) { assert(s.C.size() >= 1); // one new node s.C.conservativeResize(s.C.rows()+1,3); const int nc = s.C.rows(); assert(s.sel.size() >= 1); s.C.row(nc-1) = s.C.row(s.sel(0)); // one new bone s.BE.conservativeResize(s.BE.rows()+1,2); s.BE.row(s.BE.rows()-1) = RowVector2i(s.sel(0),nc-1); // select just last node s.sel.resize(1,1); s.sel(0) = nc-1; // reset down_C down_C = s.C; new_leaf_on_drag = false; } Eigen::Matrix4f model,proj; Eigen::Vector4f viewport; igl::opengl2::model_proj_viewport(model,proj,viewport); Eigen::Vector2f pos(mouse_x,height-mouse_y); if(new_root_on_drag) { // two new nodes s.C.conservativeResize(s.C.rows()+2,3); const int nc = s.C.rows(); Vector3d obj; int nhits = igl::embree::unproject_in_mesh( pos,model,proj,viewport,ei,obj); if(nhits == 0) { Vector3d pV_mid = igl::opengl2::project(Vcen); obj = igl::opengl2::unproject(Vector3d(mouse_x,height-mouse_y,pV_mid(2))); } s.C.row(nc-2) = obj; s.C.row(nc-1) = obj; // select last node s.sel.resize(1,1); s.sel(0) = nc-1; // one new bone s.BE.conservativeResize(s.BE.rows()+1,2); s.BE.row(s.BE.rows()-1) = RowVector2i(nc-2,nc-1); // reset down_C down_C = s.C; new_root_on_drag = false; } double z = 0; Vector3d obj,win; int nhits = igl::embree::unproject_in_mesh(pos,model,proj,viewport,ei,obj); igl::opengl2::project(obj,win); z = win(2); for(int si = 0;si 0) { pc(2) = z; } s.C.row(c) = igl::opengl2::unproject(pc); } pop_object(); pop_scene(); } glutPostRedisplay(); } void init_relative() { using namespace Eigen; using namespace igl; using namespace std; per_face_normals(V,F,N); const auto Vmax = V.colwise().maxCoeff(); const auto Vmin = V.colwise().minCoeff(); Vmid = 0.5*(Vmax + Vmin); centroid(V,F,Vcen); bbd = (Vmax-Vmin).norm(); cout<<"bbd: "< mask = selection_mask(s.sel,s.C.rows()); // stupid O(n²) matching for(int c = 0;c 0) // on same side ) { min_dist = dist; min_r = r; } } if(min_r>=0) { if(mask[min_r]) { s.C.row(c) = 0.5*(Cc.transpose()+RC.row(min_r)); }else { s.C.row(c) = RC.row(min_r); } } } pop_object(); pop_scene(); } bool save() { using namespace std; using namespace igl; if(writeTGF(output_filename,s.C,s.BE)) { cout<(0,s.C.rows()-1); break; } case 'C': case 'c': { push_undo(); // snap close vertices SparseMatrix A; adjacency_matrix(s.BE,A); VectorXi J = colon(0,s.C.rows()-1); // stupid O(n²) version for(int c = 0;c new_sel; const int old_nbe = s.BE.rows(); for(int si=0;si 0) { push_undo(); // only use first s.sel.conservativeResize(1,1); // Ideally this should only effect the connected component of s.sel(0) const auto & C = s.C; auto & BE = s.BE; vector seen(C.rows(),false); // adjacency list vector > A; adjacency_list(BE,A,false); int e = 0; queue Q; Q.push(s.sel(0)); seen[s.sel(0)] = true; while(!Q.empty()) { const int c = Q.front(); Q.pop(); for(const auto & d : A[c]) { if(!seen[d]) { BE(e,0) = c; BE(e,1) = d; e++; Q.push(d); seen[d] = true; } } } // only keep tree BE.conservativeResize(e,BE.cols()); } break; } case 'S': case 's': { save(); break; } case 'U': case 'u': { push_scene(); push_object(); for(int c = 0;c 0) { s.C.row(c) = obj; } } pop_object(); pop_scene(); break; } case 'Y': case 'y': { symmetrize(); break; } case 'z': case 'Z': is_rotating = false; is_dragging = false; if(command_down) { if(shift_down) { redo(); }else { undo(); } break; }else { push_undo(); Quaterniond q; snap_to_canonical_view_quat(camera.m_rotation_conj,1.0,q); camera.orbit(q.conjugate()); } break; default: if(!TwEventKeyboardGLUT(key,mouse_x,mouse_y)) { cout<<"Unknown key command: "< 0) { readTGF(skel_filename,s.C,s.BE); } init_relative(); ei.init(V.cast(),F.cast()); // Init glut glutInit(&argc,argv); if( !TwInit(TW_OPENGL, NULL) ) { // A fatal error occured fprintf(stderr, "AntTweakBar initialization failed: %s\n", TwGetLastError()); return 1; } // Create a tweak bar rebar.TwNewBar("TweakBar"); rebar.TwAddVarRW("camera_rotation", TW_TYPE_QUAT4D, camera.m_rotation_conj.coeffs().data(), "open readonly=true"); TwType RotationTypeTW = igl::anttweakbar::ReTwDefineEnumFromString("RotationType", "igl_trackball,two-a...-fixed-up"); rebar.TwAddVarCB( "rotation_type", RotationTypeTW, set_rotation_type,get_rotation_type,NULL,"keyIncr=] keyDecr=["); rebar.TwAddVarRW("skeleton_on_top", TW_TYPE_BOOLCPP,&skeleton_on_top,"key=O"); rebar.TwAddVarRW("wireframe", TW_TYPE_BOOLCPP,&wireframe,"key=l"); TwType SkelStyleTypeTW = igl::anttweakbar::ReTwDefineEnumFromString("SkelStyleType", "3d,vector-graphics"); rebar.TwAddVarRW("style",SkelStyleTypeTW,&skel_style,""); rebar.TwAddVarRW("alpha",TW_TYPE_DOUBLE,&alpha, "keyIncr=} keyDecr={ min=0 max=1 step=0.1"); rebar.load(REBAR_NAME); // Init antweakbar glutInitDisplayString( "rgba depth double samples>=8 "); glutInitWindowSize(glutGet(GLUT_SCREEN_WIDTH)/2.0,glutGet(GLUT_SCREEN_HEIGHT)/2.0); glutCreateWindow("skeleton-builder"); glutDisplayFunc(display); glutReshapeFunc(reshape); glutKeyboardFunc(key); glutMouseFunc(mouse); glutMotionFunc(mouse_drag); glutPassiveMotionFunc((GLUTmousemotionfun)TwEventMouseMotionGLUT); glutMainLoop(); return 0; }