计算机图形学 实现鼠标拖拽图元
问题描述
当计算机图形学中需要实现拖拽图元时,关键在于如何判断鼠标是否在拖拽,鼠标被拖拽时的移动
- 确定鼠标是在图元内部按下还是按下后移动到图元内
- 确定鼠标的移动,点击图元,图元的原点(锚点)不会瞬移至光标的位置
已知如下变量
bool isContained
反映鼠标当下位置是否在图元内,true为在图元内,false为在图元外
bool isDraging
反映鼠标此时是否被按下,true表示鼠标此时被按下,false表示鼠标此时未被按下
primitive
图元结构体,有color和position成员
解决方案
问题一
对于第一个问题,我们可以发现鼠标一共有以下三种状态
-
鼠标松开
-
鼠标按下,且鼠标在图元外
-
鼠标按下,且鼠标在图元内
我们需要做的是区分这三种状态,我们可以用一个int
型变量p
,用0,1,2分别表示这三种状态,关键在于这三种状态如何转换
0→1:当鼠标按下(isDraging
),鼠标在图元内(isContained
),鼠标上一时刻处于松开状态(p == 0
)
0→2:当鼠标按下(isDraging
),鼠标不在图元内(!isContained
),鼠标上一时刻处于松开状态(p == 0
)
1→0、2→0:当鼠标松开(!isDraging
)
这部分代码如下:
//用static变量可以让p在在循环中只初始化一次
static int p = 0;
if (isDraging)
{
if (isContained)
{
if (p == 0) p = 1;
}
else
{
if (p == 0) p = 2;
}
}
else
{
p = 0;
}
问题二
对于第二个问题,我们需要找到鼠标移动过程中的位移,我们不能直接将鼠标的NDC坐标赋值给图元坐标(这样会导致当鼠标在图元内按下时图元立刻跳到鼠标位置),而是应该将鼠标从开始拖拽那一时刻的坐标到此时鼠标的坐标的位移向量坐标加上图元上一时刻坐标赋值给图元此时坐标
具体如下:
如下图所示,鼠标由B移动到D,图元由A移动到C
p
状态0→1时记录此时的鼠标坐标B,图元位置A,计算
A
B
→
\overrightarrow{AB}
AB
p
处于状态1时,表示正在拖拽图元,鼠标坐标D,图元坐标为C,此时更新图元坐标,即
C
点
坐
标
=
D
点
坐
标
−
A
B
→
C点坐标=D点坐标-\overrightarrow{AB}
C点坐标=D点坐标−AB
代码实现如下
注:其中的csugl命名空间为自己建立的,参考逻辑实现即可,理解问题的解决方案之后就可以自己写了
void MouseDragger(const glm::ivec2 &winSize, csugl::Ref<Primitive> primitive)
{
// 鼠标在图元内
static bool isContained = false;
// 鼠标正被按下
static bool isDraging = false;
// 拖拽过程是否开始
static bool flag = false;
// 鼠标位移差
static glm::vec2 changeCurNDCpos = {0.0f, 0.0f};
// 1.获取鼠标窗口坐标:
// 2.转换为NDC坐标: (使用WindowPosToNDCPos转换)
// 3.是否在拖拽:
// 4.是否在图元内: (使用primitive的is_contained检测)
// 5.设置图元颜色:
// 6.设置图元新坐标:
float xPos, yPos;
xPos = csugl::Input::GetMousePosX();
yPos = csugl::Input::GetMousePosY();
glm::vec2 curpos = {xPos, yPos};
glm::vec2 curNDCpos = WindowPosToNDCPos(curpos, winSize);
isDraging = csugl::Input::IsMouseButtonPressed(csugl::Mouse::Button0);
isContained = primitive->is_contained(curNDCpos);
//变量p用于判断是否在图元内点击鼠标,0表示鼠标松开,1表示鼠标按下且在图元内,2表示鼠标按下且在图元外
static int p = 0;
if (isDraging) {
if (isContained) {
if (p == 0) {
changeCurNDCpos = curNDCpos - primitive->position;
p = 1;
}
} else {
if (p == 0) {
p = 2;
}
}
} else {
p = 0;
}
if(p==1) {
primitive->color = {0.1f, 0.5f, 0.8f};
primitive->position = curNDCpos - changeCurNDCpos;
} else {
primitive->color = {0.8f, 0.5f, 0.1f};
}
}
// 将窗口坐标转换为标准设备坐标(NDC)
glm::vec2 WindowPosToNDCPos(const glm::vec2 &winPosition, const glm::vec2 &winSize)
{
glm::vec2 tmpPos = winPosition / winSize * 2.0f - 1.0f; //转化为float求解
tmpPos.y = -tmpPos.y;//坐标是上下相反的
return tmpPos;
}