// Copyright (C) 2010, Guy Barrand. All rights reserved. // See the file tools.license for terms. #ifndef tools_sg_pick_action #define tools_sg_pick_action #include "matrix_action" #include "primitive_visitor" #include "../lina/plane" #include "../vmanip" #include "node" namespace tools { namespace sg { class pick_element { public: pick_element(sg::node& a_node,const std::vector& a_zs,const std::vector& a_ws,const sg::state& a_state) :m_node(a_node) ,m_zs(a_zs) ,m_ws(a_ws) ,m_state(a_state) {} virtual ~pick_element(){} public: pick_element(const pick_element& a_from) :m_node(a_from.m_node) ,m_zs(a_from.m_zs) ,m_ws(a_from.m_ws) ,m_state(a_from.m_state) {} pick_element& operator=(const pick_element& a_from){ m_zs = a_from.m_zs; m_ws = a_from.m_ws; m_state = a_from.m_state; return *this; } public: const sg::node& node() const {return m_node;} sg::node& node() {return m_node;} const std::vector& zs() const {return m_zs;} std::vector& zs() {return m_zs;} const std::vector& ws() const {return m_ws;} std::vector& ws() {return m_ws;} const sg::state& state() const {return m_state;} sg::state& state() {return m_state;} protected: sg::node& m_node; std::vector m_zs; std::vector m_ws; sg::state m_state; }; class pick_action : public matrix_action, public primitive_visitor { TOOLS_ACTION(pick_action,tools::sg::pick_action,matrix_action) protected: virtual bool project(float& a_x,float& a_y,float& a_z,float& a_w) { return parent::project_point(a_x,a_y,a_z,a_w); } virtual bool add_point(float a_x,float a_y,float a_z,float a_w) { if(is_inside(a_x,a_y,a_z,a_w)) { //we have a pick. m_done = true; return false; //to stop } return true; //continue. } virtual bool add_point(float a_x,float a_y,float a_z,float a_w, float,float,float,float) { return pick_action::add_point(a_x,a_y,a_z,a_w); } virtual bool add_line(float a_bx,float a_by,float a_bz,float a_bw, float a_ex,float a_ey,float a_ez,float a_ew) { if(is_inside(a_bx,a_by,a_bz,a_bw)) { m_done = true; return false; } if(is_inside(a_ex,a_ey,a_ez,a_ew)) { m_done = true; return false; } if(intersect_line(a_bx,a_by,a_bz,a_bw, a_ex,a_ey,a_ez,a_ew)) { m_done = true; return false; } return true; } virtual bool add_line(float a_bx,float a_by,float a_bz,float a_bw, float,float,float,float, float a_ex,float a_ey,float a_ez,float a_ew, float,float,float,float){ return pick_action::add_line(a_bx,a_by,a_bz,a_bw,a_ex,a_ey,a_ez,a_ew); } virtual bool add_triangle(float a_p1x,float a_p1y,float a_p1z,float a_p1w, float a_p2x,float a_p2y,float a_p2z,float a_p2w, float a_p3x,float a_p3y,float a_p3z,float a_p3w){ if(intersect_triangle(a_p1x,a_p1y,a_p1z,a_p1w, a_p2x,a_p2y,a_p2z,a_p2w, a_p3x,a_p3y,a_p3z,a_p3w)) { m_done = true; return false; } return true; } virtual bool add_triangle(float a_p1x,float a_p1y,float a_p1z,float a_p1w, float,float,float,float, float a_p2x,float a_p2y,float a_p2z,float a_p2w, float,float,float,float, float a_p3x,float a_p3y,float a_p3z,float a_p3w, float,float,float,float){ return pick_action::add_triangle(a_p1x,a_p1y,a_p1z,a_p1w, a_p2x,a_p2y,a_p2z,a_p2w, a_p3x,a_p3y,a_p3z,a_p3w); } virtual bool project_normal(float&,float&,float&) {return true;} virtual bool add_point_normal(float a_x,float a_y,float a_z,float a_w, float,float,float) { return pick_action::add_point(a_x,a_y,a_z,a_w); } virtual bool add_point_normal(float a_x,float a_y,float a_z,float a_w, float,float,float, float,float,float,float) { return pick_action::add_point(a_x,a_y,a_z,a_w); } virtual bool add_line_normal(float a_bx,float a_by,float a_bz,float a_bw, float,float,float, float a_ex,float a_ey,float a_ez,float a_ew, float,float,float) { return pick_action::add_line(a_bx,a_by,a_bz,a_bw,a_ex,a_ey,a_ez,a_ew); } virtual bool add_line_normal(float a_bx,float a_by,float a_bz,float a_bw, float,float,float, float,float,float,float, float a_ex,float a_ey,float a_ez,float a_ew, float,float,float, float,float,float,float) { return pick_action::add_line(a_bx,a_by,a_bz,a_bw,a_ex,a_ey,a_ez,a_ew); } virtual bool add_triangle_normal(float a_p1x,float a_p1y,float a_p1z,float a_p1w, float,float,float, float a_p2x,float a_p2y,float a_p2z,float a_p2w, float,float,float, float a_p3x,float a_p3y,float a_p3z,float a_p3w, float,float,float) { return pick_action::add_triangle(a_p1x,a_p1y,a_p1z,a_p1w, a_p2x,a_p2y,a_p2z,a_p2w, a_p3x,a_p3y,a_p3z,a_p3w); } virtual bool add_triangle_normal(float a_p1x,float a_p1y,float a_p1z,float a_p1w, float,float,float, float,float,float,float, float a_p2x,float a_p2y,float a_p2z,float a_p2w, float,float,float, float,float,float,float, float a_p3x,float a_p3y,float a_p3z,float a_p3w, float,float,float, float,float,float,float) { return pick_action::add_triangle(a_p1x,a_p1y,a_p1z,a_p1w, a_p2x,a_p2y,a_p2z,a_p2w, a_p3x,a_p3y,a_p3z,a_p3w); } public: pick_action(std::ostream& a_out,unsigned int a_ww,unsigned int a_wh,float a_l,float a_r,float a_b,float a_t) :parent(a_out,a_ww,a_wh) ,m_l(a_l) ,m_r(a_r) ,m_b(a_b) ,m_t(a_t) ,m_stop_at_first(false) //same as selection ,m_done(false) ,m_node(0) { set_to_pick_ndc(); //OPTIMIZATION } virtual ~pick_action(){} public: pick_action(const pick_action& a_from) :parent(a_from) ,primitive_visitor(a_from) ,m_l(a_from.m_l) ,m_r(a_from.m_r) ,m_b(a_from.m_b) ,m_t(a_from.m_t) ,m_stop_at_first(a_from.m_stop_at_first) ,m_done(false) ,m_node(0) { set_to_pick_ndc(); //OPTIMIZATION } pick_action& operator=(const pick_action& a_from){ parent::operator=(a_from); primitive_visitor::operator=(a_from); m_l = a_from.m_l; m_r = a_from.m_r; m_b = a_from.m_b; m_t = a_from.m_t; m_stop_at_first = a_from.m_stop_at_first; m_done = false; m_node = 0; m_zs.clear(); m_ws.clear(); m_picks.clear(); set_to_pick_ndc(); //OPTIMIZATION return *this; } public: void reset() { parent::reset(); m_done = false; m_node = 0; m_zs.clear(); m_ws.clear(); m_picks.clear(); } void set_win_size(unsigned a_ww,unsigned int a_wh) { m_ww = a_ww; m_wh = a_wh; sg::state& _state = state(); _state.m_ww = a_ww; _state.m_wh = a_wh; m_states.clear(); reset(); } void set_area(float a_l,float a_r,float a_b,float a_t) { // a_l,a_r,a_b,a_t are in window coordinates (pixels) // but handled in floats for intersection computation precision. // WARNING : we must have a_t>a_b and a_r>a_l. No check is done for that. m_l = a_l; m_r = a_r; m_b = a_b; m_t = a_t; set_to_pick_ndc(); //OPTIMIZATION } void get_area(float& a_l,float& a_r,float& a_b,float& a_t) const { a_l = m_l; a_r = m_r; a_b = m_b; a_t = m_t; } ///////////////////////////////////////////// ///////////////////////////////////////////// ///////////////////////////////////////////// void set_stop_at_first(bool a_value) {m_stop_at_first = a_value;} bool stop_at_first() const {return m_stop_at_first;} void set_done(bool a_value) {m_done = a_value;} bool done() const {return m_done;} void set_node(sg::node* a_node) {m_node = a_node;} sg::node* node() const {return m_node;} ///////////////////////////////////////////// ///////////////////////////////////////////// ///////////////////////////////////////////// typedef pick_element pick_t; void add_pick(sg::node& a_node, const std::vector& a_zs, const std::vector& a_ws, const sg::state& a_state) { m_picks.push_back(pick_t(a_node,a_zs,a_ws,a_state)); } const std::vector& picks() const {return m_picks;} void dump_picks() { m_out << "tools::sg::pick_action :" << " number of picks " << m_picks.size() << std::endl; std::vector::const_iterator it; for(it=m_picks.begin();it!=m_picks.end();++it) { m_out << " " << (*it).node().s_cls(); std::vector::const_iterator itz; for(itz=(*it).zs().begin();itz!=(*it).zs().end();++itz) { m_out << " " << *itz; } m_out << std::endl; } } pick_t* closest_pick() { if(m_picks.empty()) return 0; //closest point is minimum z ! // near -> -1 // far -> 1 // find first pick_t with not empty zs : pick_t* pck = 0; float z; std::vector::const_iterator it; for(it=m_picks.begin();it!=m_picks.end();++it) { if(minimum((*it).zs(),z)) { pck = (pick_t*)&(*it); break; } } if(!pck) return 0; it++; for(;it!=m_picks.end();++it) { float zi; if(minimum((*it).zs(),zi)) { if(zi<=z) { pck = (pick_t*)&(*it); z = zi; } } } return pck; } const std::vector& zs() const {return m_zs;} std::vector& zs() {return m_zs;} const std::vector& ws() const {return m_ws;} std::vector& ws() {return m_ws;} void set_matrices_to_identity() { projection_matrix() = m_identity; model_matrix() = m_identity; } void set_matrices_from_state() { const sg::state& _state = state(); projection_matrix() = _state.m_proj; model_matrix() = _state.m_model; } public: bool add__primitive_xy(sg::node& a_node, gl::mode_t a_mode, size_t a_floatn,const float* a_xys, bool a_stop = false, bool a_triangle_revert = false){ if(!a_floatn) return false; if(m_stop_at_first){ add_primitive_xy(a_mode,a_floatn,a_xys,a_stop,a_triangle_revert); if(m_done) { m_node = &a_node; return true; } } else { m_done = false; m_zs.clear(); add_primitive_xy(a_mode,a_floatn,a_xys,a_stop,a_triangle_revert); if(m_done) { add_pick(a_node,m_zs,m_ws,state()); m_done = false; return true; } } return false; } bool add__primitive_xy(sg::node& a_node, gl::mode_t a_mode, const std::vector& a_xys, bool a_stop = false, bool a_triangle_revert = false){ if(a_xys.empty()) return false; if(m_stop_at_first){ add_primitive_xy(a_mode,a_xys,a_stop,a_triangle_revert); if(m_done) { m_node = &a_node; return true; } } else { m_done = false; m_zs.clear(); add_primitive_xy(a_mode,a_xys,a_stop,a_triangle_revert); if(m_done) { add_pick(a_node,m_zs,m_ws,state()); m_done = false; return true; } } return false; } bool add__line_strip_xy(sg::node& a_node,size_t a_floatn,const float* a_xys,bool a_stop = false){ if(!a_floatn) return false; if(m_stop_at_first){ add_line_strip_xy(a_floatn,a_xys,a_stop); if(m_done) { m_node = &a_node; return true; } } else { m_done = false; m_zs.clear(); add_line_strip_xy(a_floatn,a_xys,a_stop); if(m_done) { add_pick(a_node,m_zs,m_ws,state()); m_done = false; return true; } } return false; } bool add__lines_xy(sg::node& a_node, const std::vector& a_xys, bool a_stop = false) { if(a_xys.empty()) return false; if(m_stop_at_first){ add_lines_xy(a_xys,a_stop); if(m_done) { m_node = &a_node; return true; } } else { m_done = false; m_zs.clear(); add_lines_xy(a_xys,a_stop); if(m_done) { add_pick(a_node,m_zs,m_ws,state()); m_done = false; return true; } } return false; } public: bool add__primitive(sg::node& a_node,gl::mode_t a_mode,size_t a_floatn,const float* a_xyzs,bool a_stop = false){ if(!a_floatn) return false; if(m_stop_at_first){ add_primitive(a_mode,a_floatn,a_xyzs,a_stop); if(m_done) { m_node = &a_node; return true; } } else { m_done = false; m_zs.clear(); add_primitive(a_mode,a_floatn,a_xyzs,a_stop); if(m_done) { add_pick(a_node,m_zs,m_ws,state()); m_done = false; return true; } } return false; } bool add__primitive(sg::node& a_node,gl::mode_t a_mode,const std::vector& a_xyzs,bool a_stop = false) { if(a_xyzs.empty()) return false; if(m_stop_at_first){ add_primitive(a_mode,a_xyzs,a_stop); if(m_done) { m_node = &a_node; return true; } } else { m_done = false; m_zs.clear(); add_primitive(a_mode,a_xyzs,a_stop); if(m_done) { add_pick(a_node,m_zs,m_ws,state()); m_done = false; return true; } } return false; } bool add__line_strip(sg::node& a_node,size_t a_floatn,const float* a_xyzs,bool a_stop = false){ if(!a_floatn) return false; if(m_stop_at_first){ add_line_strip(a_floatn,a_xyzs,a_stop); if(m_done) { m_node = &a_node; return true; } } else { m_done = false; m_zs.clear(); add_line_strip(a_floatn,a_xyzs,a_stop); if(m_done) { add_pick(a_node,m_zs,m_ws,state()); m_done = false; return true; } } return false; } bool add__triangles(sg::node& a_node, size_t a_floatn, const float* a_xyzs, bool a_stop = false){ if(!a_floatn) return false; if(m_stop_at_first){ add_triangles(a_floatn,a_xyzs,a_stop); if(m_done) { m_node = &a_node; return true; } } else { m_done = false; m_zs.clear(); add_triangles(a_floatn,a_xyzs,a_stop); if(m_done) { add_pick(a_node,m_zs,m_ws,state()); m_done = false; return true; } } return false; } bool add__lines(sg::node& a_node, const std::vector& a_xyzs, bool a_stop = false){ if(a_xyzs.empty()) return false; if(m_stop_at_first){ add_lines(a_xyzs,a_stop); if(m_done) { m_node = &a_node; return true; } } else { m_done = false; m_zs.clear(); add_lines(a_xyzs,a_stop); if(m_done) { add_pick(a_node,m_zs,m_ws,state()); m_done = false; return true; } } return false; } public: //for markers bool is_inside(float a_x,float a_y,float a_z,float a_w) { // In principle we should receive (because of proj x model matrix // mult of world coord points) point in [-1,1]x[-1,1]. float x,y; to_pick_ndc(a_x,a_y,x,y); if(x<-1) return false; if(1 ln(O,O+vec3f(0,0,1)); vec3f pz; {vec3f p1(x1,y1,a_1z); vec3f p2(x2,y2,a_2z); vec3f p3(x3,y3,a_3z); plane pl(p1,p2,p3); if(!pl.intersect(ln,pz)) { m_out << "pick_action::intersect_triangle :" << " z plane/line intersection failed." << std::endl; return false; }} // we can get w by the same plane intersection by changing the zs to ws : // (in the xy plane, 1,2,3 points have same projection, then the // z axis intersection with the triangle hits the same point done with zs // than with ws). vec3f pw; {vec3f p1(x1,y1,a_1w); vec3f p2(x2,y2,a_2w); vec3f p3(x3,y3,a_3w); plane pl(p1,p2,p3); if(!pl.intersect(ln,pw)) { m_out << "pick_action::intersect_triangle :" << " plane/line intersection failed." << std::endl; return false; }} m_zs.push_back(pz[2]); m_ws.push_back(pw[2]); return true; } protected: void set_to_pick_ndc() { //OPTIMIZATION float cx = (m_l+m_r)/2; cx /= float(m_ww); cx *= 2; float cy = (m_b+m_t)/2; cy /= float(m_wh); cy *= 2; float sx = m_r-m_l; sx /= float(m_ww); sx *= 2; float sy = m_t-m_b; sy /= float(m_wh); sy *= 2; //then now cx,cy,sx,sw in [0,2]x[0,2] coords. cx -= 1; cy -= 1; //then now cx,cy,sx,sw in [-1,1]x[-1,1] coords. m_cx = cx; m_cy = cy; m_sx = sx; m_sy = sy; } void to_pick_ndc(const float& a_fx,const float& a_fy,float& a_x,float& a_y) const { a_x = 2*(a_fx-m_cx)/m_sx; a_y = 2*(a_fy-m_cy)/m_sy; } protected: static bool ortho_clip_line(float& a_bx,float& a_by,float& a_bz,float& a_bw, float& a_ex,float& a_ey,float& a_ez,float& a_ew, /*bool a_doz,*/bool& a_toggle) { // it tests against a [-1,1]x[-1,1] box. // toggle means that at return begin contains end and end contains begin). // // begin out, end out // output : a_toggle = false, return false. // begin in, end in // output : a_toggle = false, return true. // begin in, end out // output : a_toggle = true, begin = clipping point, return true. // begin out, end in // output : a_toggle = false, begin = clipping point, return true. a_toggle = false; const unsigned int FILTER__NOZ = 0xf; //4 left right bits set to 1. bool accept = false; bool done = false; do { unsigned int bout = ortho_out(a_bx,a_by/*,a_bz,a_doz*/); unsigned int eout = ortho_out(a_ex,a_ey/*,a_ez,a_doz*/); bool reject = ( (bout & eout & FILTER__NOZ) !=0 ? true : false); if(reject) { //begin and end have a common "outside bit" raised. done = true; } else { accept = !bout && !eout; if(accept) { //begin and end have all outside-bits to zero. done = true; } else { if(!bout) { // begin inside. toggle begin and end. unsigned int tout = eout; float tx = a_ex; float ty = a_ey; float tz = a_ez; float tw = a_ew; eout = bout; a_ex = a_bx; a_ey = a_by; a_ez = a_bz; a_ew = a_bw; bout = tout; a_bx = tx; a_by = ty; a_bz = tz; a_bw = tw; a_toggle = true; } if(bout & (1<<0)) { // by > 1 float t = a_ey - a_by; t = (1 - a_by)/t; a_bx += (a_ex - a_bx) * t; a_by = 1; a_bz += (a_ez - a_bz) * t; a_bw += (a_ew - a_bw) * t; } else if(bout & (1<<1)) { // by < -1 float t = a_ey-a_by; t = (-1 - a_by)/t; a_bx += (a_ex - a_bx) * t; a_by = -1; a_bz += (a_ez - a_bz) * t; a_bw += (a_ew - a_bw) * t; } else if(bout & (1<<2)) { // bx > 1 float t = a_ex-a_bx; t = (1 - a_bx)/t; a_bx = 1; a_by += (a_ey - a_by) * t; a_bz += (a_ez - a_bz) * t; a_bw += (a_ew - a_bw) * t; } else if(bout & (1<<3)) { // bx < -1 float t = a_ex-a_bx; t = (-1 - a_bx)/t; a_bx = -1; a_by += (a_ey - a_by) * t; a_bz += (a_ez - a_bz) * t; a_bw += (a_ew - a_bw) * t; } } } } while (!done); return accept; } static unsigned int ortho_out(float a_x,float a_y/*,float a_z,bool a_doz*/){ unsigned int out = 0; if(a_y> 1) out |= (1<<0); if(a_y<-1) out |= (1<<1); if(a_x> 1) out |= (1<<2); if(a_x<-1) out |= (1<<3); return out; } protected: // picking region (in window coordinates, (0,0) = bottom-left ) : float m_l; float m_r; float m_b; float m_t; bool m_stop_at_first; bool m_done; sg::node* m_node; std::vector m_zs; std::vector m_ws; std::vector m_picks; //OPTIMIZATION: float m_cx,m_cy,m_sx,m_sy; }; }} #endif