You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

OpenGL:如何将鼠标跟随改为点击拖拽释放后移动的实现

实现OpenGL三角形的点击拖拽选目标、释放后移动交互

我来帮你搞定这个交互修改!咱们核心要做的就是引入几个状态变量来跟踪拖拽状态、移动状态以及目标位置,然后调整鼠标事件处理和动画逻辑就行。

1. 调整全局变量结构体

首先得给你的Globals结构体加几个关键变量,用来记录交互状态:

struct Globals {
    float centre_x, centre_y, rotate;
    float length;
    float mouse_x, mouse_y, speed;
    // 新增状态变量
    int is_dragging;    // 是否正在拖拽选择目标位置
    int is_moving;      // 是否正在向目标位置移动
    float target_x, target_y; // 最终要移动到的目标位置
} globals;

简单解释下:is_dragging标记是否处于拖拽选目标的状态,is_moving标记三角形是否在自动移动,target_x/y存储用户拖拽选定的终点位置。

2. 初始化新增变量

在程序初始化阶段,要给这些新变量设置合理的初始值:

void init() {
    // 保留你原来的初始化代码...
    globals.is_dragging = 0;
    globals.is_moving = 0;
    globals.target_x = globals.centre_x; // 初始目标与当前位置一致
    globals.target_y = globals.centre_y;
}

3. 处理鼠标交互事件

需要两个鼠标回调函数:一个处理鼠标按下/释放,一个处理拖拽移动:

// 鼠标按下/释放事件处理
void mouseHandler(int button, int state, int x, int y) {
    // 把GLUT的窗口坐标转成OpenGL坐标(翻转y轴,因为GLUT原点在窗口顶部)
    float opengl_y = glutGet(GLUT_WINDOW_HEIGHT) - y;
    
    if (button == GLUT_LEFT_BUTTON) {
        if (state == GLUT_DOWN) {
            // 按下左键,开始拖拽,将目标位置设为当前鼠标位置
            globals.is_dragging = 1;
            globals.target_x = x;
            globals.target_y = opengl_y;
        } else if (state == GLUT_UP) {
            // 释放左键,结束拖拽,若当前位置与目标有差异则启动移动
            globals.is_dragging = 0;
            float dx = globals.target_x - globals.centre_x;
            float dy = globals.target_y - globals.centre_y;
            // 加入距离阈值,避免浮点精度导致的抖动
            if (sqrt(dx*dx + dy*dy) > 1.0) {
                globals.is_moving = 1;
            }
        }
    }
}

// 拖拽移动事件处理
void motionHandler(int x, int y) {
    if (globals.is_dragging) {
        float opengl_y = glutGet(GLUT_WINDOW_HEIGHT) - y;
        // 拖拽过程中实时更新目标位置
        globals.target_x = x;
        globals.target_y = opengl_y;
    }
}

4. 修改动画逻辑(定时器函数)

原来的定时器是跟着鼠标实时移动,现在改成只有is_moving为true时,才向目标位置移动:

void timer(int value) {
    if (globals.is_moving) {
        // 计算当前位置到目标的向量
        float dx = globals.target_x - globals.centre_x;
        float dy = globals.target_y - globals.centre_y;
        float distance = sqrt(dx*dx + dy*dy);
        
        if (distance < 1.0) {
            // 到达目标位置,停止移动
            globals.is_moving = 0;
            globals.centre_x = globals.target_x;
            globals.centre_y = globals.target_y;
        } else {
            // 计算单位方向向量
            float dir_x = dx / distance;
            float dir_y = dy / distance;
            
            // 朝目标移动一步
            globals.centre_x += dir_x * globals.speed;
            globals.centre_y += dir_y * globals.speed;
            
            // 更新旋转角度,让三角形朝向移动方向
            globals.rotate = atan2(dir_y, dir_x) * 180.0 / PI;
        }
    }
    
    glutPostRedisplay();
    glutTimerFunc(ANIMATION_STEP, timer, 0);
}

5. 注册新的回调函数

main函数里别忘了注册新增的鼠标回调:

int main(int argc, char** argv) {
    // 保留你原来的初始化代码...
    glutMouseFunc(mouseHandler);
    glutMotionFunc(motionHandler);
    // 保留其他注册代码...
    glutMainLoop();
    return 0;
}

完整整合代码示例

把所有部分整合后的完整代码如下:

#include <GL/glut.h>
#include <math.h>

#define ANIMATION_STEP (1000/300)
#define PI 3.1415926535897932

struct Globals {
    float centre_x, centre_y, rotate;
    float length;
    float mouse_x, mouse_y, speed;
    int is_dragging;
    int is_moving;
    float target_x, target_y;
} globals;

void init() {
    globals.centre_x = 400.0f;
    globals.centre_y = 300.0f;
    globals.rotate = 0.0f;
    globals.length = 50.0f;
    globals.speed = 2.0f;
    globals.is_dragging = 0;
    globals.is_moving = 0;
    globals.target_x = globals.centre_x;
    globals.target_y = globals.centre_y;
    
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, 800, 0, 600); // 假设窗口大小为800x600
}

void drawTriangle() {
    glPushMatrix();
    glTranslatef(globals.centre_x, globals.centre_y, 0.0f);
    glRotatef(globals.rotate, 0.0f, 0.0f, 1.0f);
    
    glBegin(GL_TRIANGLES);
    glColor3f(1.0f, 0.0f, 0.0f);
    glVertex2f(globals.length, 0.0f);
    glColor3f(0.0f, 1.0f, 0.0f);
    glVertex2f(-globals.length/2, globals.length*sqrt(3)/2);
    glColor3f(0.0f, 0.0f, 1.0f);
    glVertex2f(-globals.length/2, -globals.length*sqrt(3)/2);
    glEnd();
    
    glPopMatrix();
}

void display() {
    glClear(GL_COLOR_BUFFER_BIT);
    drawTriangle();
    glutSwapBuffers();
}

void mouseHandler(int button, int state, int x, int y) {
    float opengl_y = glutGet(GLUT_WINDOW_HEIGHT) - y;
    
    if (button == GLUT_LEFT_BUTTON) {
        if (state == GLUT_DOWN) {
            globals.is_dragging = 1;
            globals.target_x = x;
            globals.target_y = opengl_y;
        } else if (state == GLUT_UP) {
            globals.is_dragging = 0;
            float dx = globals.target_x - globals.centre_x;
            float dy = globals.target_y - globals.centre_y;
            if (sqrt(dx*dx + dy*dy) > 1.0) {
                globals.is_moving = 1;
            }
        }
    }
}

void motionHandler(int x, int y) {
    if (globals.is_dragging) {
        float opengl_y = glutGet(GLUT_WINDOW_HEIGHT) - y;
        globals.target_x = x;
        globals.target_y = opengl_y;
    }
}

void timer(int value) {
    if (globals.is_moving) {
        float dx = globals.target_x - globals.centre_x;
        float dy = globals.target_y - globals.centre_y;
        float distance = sqrt(dx*dx + dy*dy);
        
        if (distance < 1.0) {
            globals.is_moving = 0;
            globals.centre_x = globals.target_x;
            globals.centre_y = globals.target_y;
        } else {
            float dir_x = dx / distance;
            float dir_y = dy / distance;
            
            globals.centre_x += dir_x * globals.speed;
            globals.centre_y += dir_y * globals.speed;
            
            globals.rotate = atan2(dir_y, dir_x) * 180.0 / PI;
        }
    }
    
    glutPostRedisplay();
    glutTimerFunc(ANIMATION_STEP, timer, 0);
}

int main(int argc, char** argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowSize(800, 600);
    glutCreateWindow("Click-Drag Move Triangle");
    
    init();
    glutDisplayFunc(display);
    glutMouseFunc(mouseHandler);
    glutMotionFunc(motionHandler);
    glutTimerFunc(ANIMATION_STEP, timer, 0);
    
    glutMainLoop();
    return 0;
}

关键说明

  • 拖拽过程中,目标位置会实时跟随鼠标移动,释放左键后三角形会自动向目标位置移动
  • 移动过程中三角形会始终朝向移动方向,和你原来的旋转逻辑保持一致
  • 加入了距离阈值(1.0),避免因浮点精度问题导致的微小抖动

内容的提问来源于stack exchange,提问作者Alonzo Robbe

火山引擎 最新活动