BambuStudio/libigl/igl/opengl2/MouseController.h

692 lines
20 KiB
C
Raw Normal View History

2024-12-20 06:44:50 +00:00
// This file is part of libigl, a simple c++ geometry processing library.
//
// Copyright (C) 2013 Alec Jacobson <alecjacobson@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla Public License
// v. 2.0. If a copy of the MPL was not distributed with this file, You can
// obtain one at http://mozilla.org/MPL/2.0/.
#ifndef IGL_OPENGL2_MOUSECONTROLLER_H
#define IGL_OPENGL2_MOUSECONTROLLER_H
// Needs to be included before others
#include <Eigen/StdVector>
#include "RotateWidget.h"
#include "TranslateWidget.h"
#include <Eigen/Core>
#include <Eigen/Geometry>
#include <vector>
// Class for control a skeletal FK rig with the mouse.
namespace igl
{
namespace opengl2
{
class MouseController
{
public:
typedef Eigen::VectorXi VectorXb;
// Propagate selection to descendants so that selected bones and their
// subtrees are all selected.
//
// Input:
// S #S list of whether selected
// P #S list of bone parents
// Output:
// T #S list of whether selected
static inline void propogate_to_descendants_if(
const VectorXb & S,
const Eigen::VectorXi & P,
VectorXb & T);
// Create a matrix of colors for the selection and their descendants.
//
// Inputs:
// selection #S list of whether a bone is selected
// selected_color color for selected bones
// unselected_color color for unselected bones
// Outputs:
// C #P by 4 list of colors
static inline void color_if(
const VectorXb & S,
const Eigen::Vector4f & selected_color,
const Eigen::Vector4f & unselected_color,
Eigen::MatrixXf & C);
enum WidgetMode
{
WIDGET_MODE_ROTATE = 0,
WIDGET_MODE_TRANSLATE = 1,
NUM_WIDGET_MODES = 2,
};
private:
// m_is_selecting whether currently selecting
// m_selection #m_rotations list of whether a bone is selected
// m_down_x x-coordinate of mouse location at down
// m_down_y y-coordinate 〃
// m_drag_x x-coordinate of mouse location at drag
// m_drag_y y-coordinate 〃
// m_widget rotation widget for selected bone
// m_width width of containing window
// m_height height 〃
// m_rotations list of rotations for each bone
// m_rotations_at_selection list of rotations for each bone at time of
// selection
// m_translations list of translations for each bone
// m_fk_rotations_at_selection list of rotations for each bone at time of
// selection
// m_root_enabled Whether root is enabled
bool m_is_selecting;
VectorXb m_selection;
int m_down_x,m_down_y,m_drag_x,m_drag_y;
int m_width,m_height;
igl::opengl2::RotateWidget m_widget;
igl::opengl2::TranslateWidget m_trans_widget;
Eigen::Quaterniond m_widget_rot_at_selection;
//Eigen::Vector3d m_trans_widget_trans_at_selection;
typedef std::vector<
Eigen::Quaterniond,
Eigen::aligned_allocator<Eigen::Quaterniond> > RotationList;
typedef std::vector< Eigen::Vector3d > TranslationList;
RotationList
m_rotations,
m_rotations_at_selection,
m_fk_rotations_at_selection,
m_parent_rotations_at_selection;
TranslationList
m_translations,
m_translations_at_selection,
m_fk_translations_at_selection;
bool m_root_enabled;
WidgetMode m_widget_mode;
public:
MouseController();
// Returns const reference to m_selection
inline const VectorXb & selection() const{return m_selection;};
// 〃 m_is_selecting
inline const bool & is_selecting() const{return m_is_selecting;}
inline bool is_widget_down() const{return m_widget.is_down();}
inline bool is_trans_widget_down() const{return m_trans_widget.is_down();}
// 〃 m_rotations
inline const RotationList & rotations() const{return m_rotations;}
inline const TranslationList & translations() const{return m_translations;}
// Returns non-const reference to m_root_enabled
inline bool & root_enabled(){ return m_root_enabled;}
inline void reshape(const int w, const int h);
// Process down, drag, up mouse events
//
// Inputs:
// x x-coordinate of mouse click with respect to container
// y y-coordinate 〃
// Returns true if accepted (action taken).
inline bool down(const int x, const int y);
inline bool drag(const int x, const int y);
inline bool up(const int x, const int y);
// Draw selection box and widget
inline void draw() const;
// Set `m_selection` based on the last drag selection and initialize
// widget.
//
// Inputs:
// C #C by dim list of joint positions at rest
// BE #BE by 2 list of bone indices at rest
// P #P list of bone parents
inline void set_selection_from_last_drag(
const Eigen::MatrixXd & C,
const Eigen::MatrixXi & BE,
const Eigen::VectorXi & P,
const Eigen::VectorXi & RP);
// Set from explicit selection
inline void set_selection(
const Eigen::VectorXi & S,
const Eigen::MatrixXd & C,
const Eigen::MatrixXi & BE,
const Eigen::VectorXi & P,
const Eigen::VectorXi & RP);
// Set size of skeleton
//
// Inputs:
// n number of bones
inline void set_size(const int n);
// Resets m_rotation elements to identity
inline void reset();
inline void reset_selected();
inline void reset_rotations();
inline void reset_selected_rotations();
inline void reset_translations();
inline void reset_selected_translations();
inline bool set_rotations(const RotationList & vQ);
inline bool set_translations(const TranslationList & vT);
// Sets all entries in m_selection to false
inline void clear_selection();
// Returns true iff some element in m_selection is true
inline bool any_selection() const;
inline void set_widget_mode(const WidgetMode & mode);
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
}
}
// Implementation
#include "../line_segment_in_rectangle.h"
#include "draw_rectangular_marquee.h"
#include "project.h"
#include "../forward_kinematics.h"
#include <iostream>
#include <algorithm>
#include <functional>
inline void igl::opengl2::MouseController::propogate_to_descendants_if(
const VectorXb & S,
const Eigen::VectorXi & P,
VectorXb & T)
{
using namespace std;
const int n = S.rows();
assert(P.rows() == n);
// dynamic programming
T = S;
vector<bool> seen(n,false);
// Recursively look up chain and see if ancestor is selected
const function<bool(int)> look_up = [&](int e) -> bool
{
if(e==-1)
{
return false;
}
if(!seen[e])
{
seen[e] = true;
T(e) |= look_up(P(e));
}
return T(e);
};
for(int e = 0;e<n;e++)
{
if(!seen[e])
{
T(e) = look_up(e);
}
}
}
inline void igl::opengl2::MouseController::color_if(
const VectorXb & S,
const Eigen::Vector4f & selected_color,
const Eigen::Vector4f & unselected_color,
Eigen::MatrixXf & C)
{
C.resize(S.rows(),4);
for(int e=0;e<S.rows();e++)
{
C.row(e) = S(e)?selected_color:unselected_color;
}
}
inline igl::opengl2::MouseController::MouseController():
m_is_selecting(false),
m_selection(),
m_down_x(-1),m_down_y(-1),m_drag_x(-1),m_drag_y(-1),
m_width(-1),m_height(-1),
m_widget(),
m_widget_rot_at_selection(),
//m_trans_widget_trans_at_selection(),
m_trans_widget(),
m_rotations(),
m_translations(),
m_rotations_at_selection(),
m_root_enabled(true),
m_widget_mode(WIDGET_MODE_ROTATE)
{
}
inline void igl::opengl2::MouseController::reshape(const int w, const int h)
{
m_width = w;
m_height = h;
}
inline bool igl::opengl2::MouseController::down(const int x, const int y)
{
using namespace std;
m_down_x = m_drag_x =x;
m_down_y = m_drag_y =y;
const bool widget_down = any_selection() &&
(
(m_widget_mode == WIDGET_MODE_ROTATE && m_widget.down(x,m_height-y)) ||
(m_widget_mode == WIDGET_MODE_TRANSLATE &&
m_trans_widget.down(x,m_height-y))
);
if(!widget_down)
{
m_is_selecting = true;
}
return m_is_selecting || widget_down;
}
inline bool igl::opengl2::MouseController::drag(const int x, const int y)
{
using namespace std;
using namespace Eigen;
m_drag_x = x;
m_drag_y = y;
if(m_is_selecting)
{
return m_is_selecting;
}else
{
switch(m_widget_mode)
{
default: // fall through
case WIDGET_MODE_ROTATE:
{
if(!m_widget.drag(x,m_height-y))
{
return false;
}
assert(any_selection());
assert(m_selection.size() == (int)m_rotations.size());
assert(m_selection.size() == (int)m_translations.size());
for(int e = 0;e<m_selection.size();e++)
{
if(m_selection(e))
{
// Let:
// w.θr = w.θ ⋅ w.θ₀*
// w.θr takes (absolute) frame of w.θ₀ to w.θ:
// w.θ = w.θr ⋅ w.θ₀
// Define:
// w.θ₀ = θfk ⋅ θx,
// the absolute rotation of the x axis to the deformed bone at
// selection. Likewise,
// w.θ = θfk' ⋅ θx,
// the current absolute rotation of the x axis to the deformed bone.
// Define recursively:
// θfk = θfk(p) ⋅ Θr,
// then because we're only changeing this relative rotation
// θfk' = θfk(p) ⋅ Θr ⋅ θr* ⋅ θr'
// θfk' = θfk ⋅ θr* ⋅ θr'
// w.θ ⋅ θx* = θfk ⋅ θr* ⋅ θr'
// θr ⋅ θfk* ⋅ w.θ ⋅ θx* = θr'
// θr ⋅ θfk* ⋅ w.θr ⋅ w.θ₀ ⋅ θx* = θr'
// θr ⋅ θfk* ⋅ w.θr ⋅ θfk ⋅θx ⋅ θx* = θr'
// θr ⋅ θfk* ⋅ w.θr ⋅ θfk = θr'
// which I guess is the right multiply change after being changed to
// the bases of θfk, the rotation of the bone relative to its rest
// frame.
//
const Quaterniond & frame = m_fk_rotations_at_selection[e];
m_rotations[e] =
m_rotations_at_selection[e] *
frame.conjugate() *
(m_widget.rot*m_widget_rot_at_selection.conjugate()) *
frame;
}
}
}
case WIDGET_MODE_TRANSLATE:
{
if(!m_trans_widget.drag(x,m_height-y))
{
return false;
}
assert(any_selection());
assert(m_selection.size() == (int)m_rotations.size());
assert(m_selection.size() == (int)m_translations.size());
for(int e = 0;e<m_selection.size();e++)
{
if(m_selection(e))
{
m_translations[e] =
m_translations_at_selection[e] +
m_parent_rotations_at_selection[e].conjugate()*
m_trans_widget.m_trans;
}
}
}
}
return true;
}
}
inline bool igl::opengl2::MouseController::up(const int x, const int y)
{
m_is_selecting = false;
m_widget.up(x,m_height-y);
m_trans_widget.up(x,m_height-y);
return false;
}
inline void igl::opengl2::MouseController::draw() const
{
if(any_selection())
{
switch(m_widget_mode)
{
default:
case WIDGET_MODE_ROTATE:
m_widget.draw();
break;
case WIDGET_MODE_TRANSLATE:
m_trans_widget.draw();
break;
}
}
if(m_is_selecting)
{
// Remember settings
GLboolean dt;
glGetBooleanv(GL_DEPTH_TEST,&dt);
int old_vp[4];
glGetIntegerv(GL_VIEWPORT,old_vp);
// True screen space
glViewport(0,0,m_width,m_height);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
gluOrtho2D(0,m_width,0,m_height);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glDisable(GL_DEPTH_TEST);
draw_rectangular_marquee(
m_down_x,
m_height-m_down_y,
m_drag_x,
m_height-m_drag_y);
// Restore settings
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glViewport(old_vp[0],old_vp[1],old_vp[2],old_vp[3]);
dt?glEnable(GL_DEPTH_TEST):glDisable(GL_DEPTH_TEST);
}
}
inline void igl::opengl2::MouseController::set_selection_from_last_drag(
const Eigen::MatrixXd & C,
const Eigen::MatrixXi & BE,
const Eigen::VectorXi & P,
const Eigen::VectorXi & RP)
{
using namespace Eigen;
using namespace std;
m_rotations_at_selection = m_rotations;
m_translations_at_selection = m_translations;
assert(BE.rows() == P.rows());
m_selection = VectorXb::Zero(BE.rows());
// m_rotation[e] is the relative rotation stored at bone e (as seen by the
// joint traveling with its parent)
// vQ[e] is the absolute rotation of a bone at rest to its current position:
// vQ[e] = vQ[p(e)] * m_rotation[e]
vector<Quaterniond,aligned_allocator<Quaterniond> > vQ;
vector<Vector3d> vT;
forward_kinematics(C,BE,P,m_rotations,m_translations,vQ,vT);
// Loop over deformed bones
for(int e = 0;e<BE.rows();e++)
{
Affine3d a = Affine3d::Identity();
a.translate(vT[e]);
a.rotate(vQ[e]);
Vector3d s = a * (Vector3d)C.row(BE(e,0));
Vector3d d = a * (Vector3d)C.row(BE(e,1));
Vector3d projs = project(s);
Vector3d projd = project(d);
m_selection(e) = line_segment_in_rectangle(
projs.head(2),projd.head(2),
Vector2d(m_down_x,m_height-m_down_y),
Vector2d(m_drag_x,m_height-m_drag_y));
}
return set_selection(m_selection,C,BE,P,RP);
}
inline void igl::opengl2::MouseController::set_selection(
const Eigen::VectorXi & S,
const Eigen::MatrixXd & C,
const Eigen::MatrixXi & BE,
const Eigen::VectorXi & P,
const Eigen::VectorXi & RP)
{
using namespace Eigen;
using namespace std;
vector<Quaterniond,aligned_allocator<Quaterniond> > & vQ =
m_fk_rotations_at_selection;
vector<Vector3d> & vT = m_fk_translations_at_selection;
forward_kinematics(C,BE,P,m_rotations,m_translations,vQ,vT);
m_parent_rotations_at_selection.resize(
m_rotations.size(),Quaterniond::Identity());
for(size_t r = 0;r<vQ.size();r++)
{
if(P(r)>=0)
{
m_parent_rotations_at_selection[r] = vQ[P(r)];
}
}
if(&m_selection != &S)
{
m_selection = S;
}
assert(m_selection.rows() == BE.rows());
assert(BE.rows() == P.rows());
assert(BE.rows() == RP.rows());
// Zero-out S up a path of ones from e
auto propagate = [&](const int e, const VectorXb & S, VectorXb & N)
{
if(S(e))
{
int f = e;
while(true)
{
int p = P(f);
if(p==-1||!S(p))
{
break;
}
N(f) = false;
f = p;
}
}
};
VectorXb prev_selection = m_selection;
// Combine upward, group rigid parts, repeat
while(true)
{
// Spread selection across rigid pieces
VectorXb SRP(VectorXb::Zero(RP.maxCoeff()+1));
for(int e = 0;e<BE.rows();e++)
{
SRP(RP(e)) |= m_selection(e);
}
for(int e = 0;e<BE.rows();e++)
{
m_selection(e) = SRP(RP(e));
}
// Clear selections below m_selection ancestors
VectorXb new_selection = m_selection;
for(int e = 0;e<P.rows();e++)
{
propagate(e,m_selection,new_selection);
}
m_selection = new_selection;
if(m_selection==prev_selection)
{
break;
}
prev_selection = m_selection;
}
// Now selection should contain just bone roots of m_selection subtrees
if(m_selection.array().any())
{
// Taking average
Vector3d avg_pos(0,0,0);
//m_trans_widget_trans_at_selection.setConstant(0);
m_widget_rot_at_selection.coeffs().setConstant(0);
m_widget.rot.coeffs().array().setConstant(0);
Quaterniond cur_rot(0,0,0,0);
int num_selection = 0;
// Compute average widget for selection
for(int e = 0;e<BE.rows();e++)
{
if(m_selection(e))
{
Vector3d s = C.row(BE(e,0));
Vector3d d = C.row(BE(e,1));
auto b = (d-s).transpose().eval();
{
Affine3d a = Affine3d::Identity();
a.translate(vT[e]);
a.rotate(vQ[e]);
avg_pos += a*s;
}
// Rotation of x axis to this bone
Quaterniond rot_at_bind;
rot_at_bind.setFromTwoVectors(Vector3d(1,0,0),b);
const Quaterniond abs_rot = vQ[e] * rot_at_bind;
m_widget_rot_at_selection.coeffs() += abs_rot.coeffs();
//m_trans_widget_trans_at_selection += vT[e];
num_selection++;
}
}
// Take average
avg_pos.array() /= (double)num_selection;
//m_trans_widget_trans_at_selection.array() /= (double)num_selection;
m_widget_rot_at_selection.coeffs().array() /= (double)num_selection;
m_widget_rot_at_selection.normalize();
m_widget.rot = m_widget_rot_at_selection;
m_widget.pos = avg_pos;
m_trans_widget.m_pos = avg_pos;
//m_trans_widget.m_trans = m_trans_widget_trans_at_selection;
m_trans_widget.m_trans.setConstant(0);
}
m_widget.m_is_enabled = true;
m_trans_widget.m_is_enabled = true;
for(int s = 0;s<m_selection.rows();s++)
{
// a root is selected then disable.
if(!m_root_enabled && m_selection(s) && P(s) == -1)
{
m_widget.m_is_enabled = false;
m_trans_widget.m_is_enabled = false;
break;
}
}
}
inline void igl::opengl2::MouseController::set_size(const int n)
{
using namespace Eigen;
clear_selection();
m_rotations.clear();
m_rotations.resize(n,Quaterniond::Identity());
m_translations.clear();
m_translations.resize(n,Vector3d(0,0,0));
m_selection = VectorXb::Zero(n);
}
inline void igl::opengl2::MouseController::reset()
{
reset_rotations();
reset_translations();
}
inline void igl::opengl2::MouseController::reset_selected()
{
reset_selected_rotations();
reset_selected_translations();
}
inline void igl::opengl2::MouseController::reset_rotations()
{
using namespace Eigen;
using namespace std;
fill(m_rotations.begin(),m_rotations.end(),Quaterniond::Identity());
// cop out. just clear selection
clear_selection();
}
inline void igl::opengl2::MouseController::reset_selected_rotations()
{
using namespace Eigen;
for(int e = 0;e<m_selection.size();e++)
{
if(m_selection(e))
{
m_rotations[e] = Quaterniond::Identity();
}
}
}
inline void igl::opengl2::MouseController::reset_translations()
{
using namespace Eigen;
using namespace std;
fill(m_translations.begin(),m_translations.end(),Vector3d(0,0,0));
// cop out. just clear selection
clear_selection();
}
inline void igl::opengl2::MouseController::reset_selected_translations()
{
using namespace Eigen;
for(int e = 0;e<m_selection.size();e++)
{
if(m_selection(e))
{
m_translations[e] = Vector3d(0,0,0);
}
}
}
inline bool igl::opengl2::MouseController::set_rotations(const RotationList & vQ)
{
if(vQ.size() != m_rotations.size())
{
return false;
}
assert(!any_selection());
m_rotations = vQ;
return true;
}
inline bool igl::opengl2::MouseController::set_translations(const TranslationList & vT)
{
if(vT.size() != m_translations.size())
{
return false;
}
assert(!any_selection());
m_translations = vT;
return true;
}
inline void igl::opengl2::MouseController::clear_selection()
{
m_selection.setConstant(false);
}
inline bool igl::opengl2::MouseController::any_selection() const
{
return m_selection.array().any();
}
inline void igl::opengl2::MouseController::set_widget_mode(const WidgetMode & mode)
{
switch(m_widget_mode)
{
default:
case WIDGET_MODE_TRANSLATE:
m_widget.pos = m_trans_widget.m_pos+m_trans_widget.m_trans;
break;
case WIDGET_MODE_ROTATE:
break;
}
m_widget_mode = mode;
}
#endif