首页 > 其他分享 >OpenGL 如何实现 trackball

OpenGL 如何实现 trackball

时间:2024-11-09 18:18:48浏览次数:1  
标签:鼠标 OpenGL int 0.0 矩阵 旋转 如何 GLfloat trackball

在 OpenGL 中实现 trackball(轨迹球)功能可以让用户通过鼠标操作来旋转场景中的物体,就好像在操作一个虚拟的轨迹球一样。以下是一种常见的实现方式的步骤:

  1. 基本原理
    Trackball 的基本思想是将二维的鼠标移动映射到三维空间中的旋转操作。当用户在屏幕上按下鼠标并移动时,根据鼠标的起始位置和当前位置,计算出一个旋转轴和旋转角度,然后将这个旋转应用到场景中的物体上,实现物体的旋转效果。
  2. 准备工作
    包含必要的头文件:通常需要包含<GL/glut.h>(如果使用 GLUT 库进行窗口管理和事件处理)以及其他可能用到的数学库头文件,比如用于数学计算。
    设置窗口和 OpenGL 环境:创建一个 OpenGL 窗口,初始化 OpenGL 的渲染环境,包括设置投影方式、清除颜色等。例如:
    cpp
    复制
#include <GL/glut.h>
#include <cmath>

// 窗口大小
const int WIDTH = 800;
const int HEIGHT = 600;

// 全局变量,用于存储物体的旋转矩阵
GLfloat rotationMatrix[16];

// 初始化OpenGL环境
void init() {
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0, (GLfloat) WIDTH / (GLfloat) HEIGHT, 0.1, 100.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    // 初始化为单位矩阵,代表没有旋转
    for (int i = 0; i < 16; i++) {
        rotationMatrix[i] = 0.0;
        if (i % 5 == 0) {
            rotationMatrix[i] = 1.0;
        }
    }
}

在上述代码中,我们初始化了 OpenGL 的清除颜色、投影矩阵和模型视图矩阵,并将用于存储物体旋转矩阵的rotationMatrix初始化为单位矩阵。
3. 鼠标事件处理
鼠标按下事件:当用户按下鼠标时,记录鼠标的初始位置坐标。可以使用 GLUT 库提供的鼠标事件回调函数来实现。例如:
cpp
复制

// 鼠标按下时的坐标
int mouseXInitial, mouseYInitial;

// 鼠标按下事件回调函数
void mousePressed(int button, int state, int x, int y) {
    if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
        mouseXInitial = x;
        mouseYInitial = y;
    }
}

在上述代码中,当用户按下鼠标左键时,将鼠标的当前位置坐标x和y分别存储到mouseXInitial和mouseYInitial变量中。
鼠标移动事件:当用户在按下鼠标左键的情况下移动鼠标时,根据鼠标的初始位置和当前位置计算出旋转轴和旋转角度,然后更新物体的旋转矩阵。例如:
cpp
复制

// 计算两点之间的距离
GLfloat distance(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2) {
    return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
}

// 根据鼠标移动计算旋转矩阵
void mouseMoved(int x, int y) {
    GLfloat dx = x - mouseXInitial;
    GLfloat dy = y - mouseYInitial;

    // 计算旋转角度
    GLfloat angle = distance(mouseXInitial, mouseYInitial, x, y) * 0.01;

    // 计算旋转轴
    GLfloat axisX = dy;
    GLfloat axisY = -dx;
    GLfloat axisZ = 0.0;

    // 规范化旋转轴向量
    GLfloat length = distance(0.0, 0.0, axisX, axisY, axisZ);
    if (length!= 0.0) {
        axisX /= length;
        axisY /= length;
        axisZ /= length;
    }

    // 创建临时旋转矩阵
    GLfloat tempMatrix[16];
    gluRotationMatrixARB(angle, axisX, axisY, axisZ);

    // 更新物体的旋转矩阵
    glMultMatrixf(tempMatrix);
    for (int i = 0; i < 16; i++) {
        rotationMatrix[i] = tempMatrix[i];
    }

    // 重新渲染场景
    glutPostRedraw();
}

在上述代码中:
首先根据鼠标的初始位置和当前位置计算出dx和dy,进而计算出旋转角度angle和旋转轴的坐标axisX、axisY和axisZ。
然后通过gluRotationMatrixARB函数创建一个临时旋转矩阵tempMatrix,并将其与当前的物体旋转矩阵相乘,更新物体的旋转矩阵rotationMatrix。
最后通过glutPostRedraw()函数请求重新渲染场景,以显示出物体旋转后的效果。
4. 渲染场景
在渲染场景时,应用物体的旋转矩阵到模型视图矩阵上,然后绘制场景中的物体。例如:
cpp
复制

// 绘制场景中的物体
void drawScene() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    // 应用旋转矩阵
    glMultMatrixf(rotationMatrix);

    // 这里可以绘制具体的物体,比如一个立方体
    glutWireCube(1.0);

    glutSwapBuffers();
}

在上述代码中,首先清除颜色缓冲和深度缓冲,然后将模型视图矩阵设置为单位矩阵,接着应用物体的旋转矩阵rotationMatrix,最后绘制一个立方体(这里只是示例,实际可以绘制任何想要的物体),并通过glutSwapBuffers()函数交换前后缓冲,显示出渲染后的场景。
5. 主函数设置
在主函数中,需要设置 GLUT 库的相关参数,包括窗口大小、显示模式、回调函数等,启动 GLUT 的事件循环。例如:
cpp
复制

int main(int argc, char** argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(WIDTH, HEIGHT);
    glutInitWindowPosition(200, 200);
    glutCreateWindow("OpenGL Trackball");

    init();

    glutMouseFunc(mousePressed);
    glutMotionFunc(mouseMoved);
    glutDisplayFunc(drawScene);

    glutMainLoop();

    return 0;
}

在上述代码中,我们初始化了 GLUT 库,设置了窗口的显示模式、大小和位置,创建了窗口,初始化了 OpenGL 环境,设置了鼠标事件、显示事件等回调函数,最后启动了 GLUT 的事件循环,使程序能够正常运行并处理各种事件。
通过以上步骤,就可以在 OpenGL 中实现一个基本的 trackball 功能,让用户通过鼠标操作来旋转场景中的物体。当然,这只是一种常见的实现方式,实际应用中可能需要根据具体需求进行调整和完善。

======================================================================================

如何处理鼠标移动事件来更新旋转矩阵?
以下是详细介绍如何处理鼠标移动事件来更新旋转矩阵以实现类似 trackball 的功能:

  1. 基本思路
    当鼠标在按下状态下移动时,我们需要根据鼠标起始位置和当前位置来确定一个旋转轴和旋转角度,然后利用这些信息生成一个旋转矩阵,并将其与当前的物体旋转矩阵相乘,从而更新物体的旋转状态。
  2. 具体步骤
    (1)获取鼠标移动的偏移量
    在鼠标按下事件发生后,记录下鼠标的初始位置(设为)。当鼠标移动时,获取当前鼠标位置(设为),通过计算两者在水平和垂直方向上的偏移量来确定鼠标的移动情况。
    cpp
    复制
// 鼠标按下时的坐标
int mouseXInitial, mouseYInitial;

// 鼠标移动事件回调函数
void mouseMoved(int x, int y) {
    GLfloat dx = x - mouseXInitial;
    GLfloat dy = y - mouseYInitial;
    // 后续步骤将基于dx和dy来计算旋转轴和旋转角度
}

(2)计算旋转角度
一种常见的计算旋转角度的方法是根据鼠标移动的距离来确定。可以使用两点间距离公式计算鼠标起始位置和当前位置之间的距离,然后将这个距离映射到一个合适的角度范围。
cpp
复制

// 计算两点之间的距离
GLfloat distance(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2) {
    return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
}

// 鼠标移动事件回调函数
void mouseMoved(int x, int y) {
    GLfloat dx = x - mouseXInitial;
    GLfloat dy = y - mouseYInitial;

    // 计算旋转角度
    GLfloat angle = distance(mouseXInitial, mouseYInitial, x, y) * 0.01;
    // 后续步骤将基于angle来生成旋转矩阵
}

在上述代码中,通过distance函数计算出鼠标移动的距离,然后乘以一个缩放因子(这里是0.01)来得到旋转角度,你可以根据实际需求调整这个缩放因子以获得合适的旋转灵敏度。
(3)计算旋转轴
旋转轴的确定通常基于鼠标在屏幕上的移动方向。一种简单的方法是将垂直方向的移动映射到旋转轴的x分量,将水平方向的移动映射到旋转轴的y分量,而z分量可以初始设置为0(对于二维屏幕到三维旋转的简单映射情况)。
cpp
复制

// 鼠标移动事件回调函数
void mouseMoved(int x, int y) {
    GLfloat dx = x - mouseXInitial;
    GLfloat dy = y - mouseYInitial;

    GLfloat angle = distance(mouseXInitial, mouseYInitial, x, y) * 0.01;

    // 计算旋转轴
    GLfloat axisX = dy;
    GLfloat axisY = -dx;
    GLfloat axisZ = 0.0;

    // 后续步骤将对旋转轴进行规范化处理并生成旋转矩阵
}

这里将水平移动取反是为了符合右手定则等常见的旋转规则,使得鼠标移动方向与物体旋转方向在直观上相匹配。
(4)规范化旋转轴向量
计算得到的旋转轴向量可能不是单位向量,为了确保后续生成旋转矩阵的准确性,需要将其规范化,即将其长度变为1。
cpp
复制

// 鼠标移动事件回调函数
void mouseMoved(int x, int y) {
    GLfloat dx = x - mouseXInitial;
    GLfloat dy = y - mouseYInitial;

    GLfloat angle = distance(mouseXInitial, mouseYInitial, x, y) * 0.01;

    GLfloat axisX = dy;
    GLfloat axisY = -dx;
    GLfloat axisZ = 0.0;

    // 规范化旋转轴向量
    GLfloat length = distance(0.0, 0.0, axisX, axisY, axisZ);
    if (length!= 0.0) {
        axisX /= length;
        axisY /= length;
        axisZ /= length;
    }

    // 后续步骤将基于规范化后的旋转轴和旋转角度生成旋转矩阵
}

(5)生成旋转矩阵
利用规范化后的旋转轴和计算得到的旋转角度,可以通过 OpenGL 提供的函数(如gluRotationMatrixARB)来生成一个旋转矩阵。
cpp
复制

// 鼠标移动事件回调函数
void mouseMoved(int x, int y) {
    GLfloat dx = x - mouseXInitial;
    GLfloat dy = y - mouseYInitial;

    GLfloat angle = distance(mouseXInitial, mouseYInitial, x, y) * 0.01;

    GLfloat axisX = dy;
    GLfloat axisY = -dx;
    GLfloat axisZ = 0.0;

    GLfloat length = distance(0.0, 0.0, axisX, axisY, axisZ);
    if (length!= 0.0) {
        axisX /= length;
        axisY /= length;
        axisZ /= length;
    }

    // 创建临时旋转矩阵
    GLfloat tempMatrix[16];
    gluRotationMatrixARB(angle, axisX, axisY, axisZ);

    // 后续步骤将用临时旋转矩阵更新物体的旋转矩阵
}

(6)更新物体的旋转矩阵
将生成的临时旋转矩阵与当前物体的旋转矩阵相乘,得到更新后的旋转矩阵,以反映物体新的旋转状态。
cpp
复制

// 全局变量,用于存储物体的旋转矩阵
GLfloat rotationMatrix[16];

// 鼠标移动事件回调函数
void mouseMoved(int x, int y) {
    GLfloat dx = x - mouseXInitial;
    GLfloat dy = y - mouseYInitial;

    GLfloat angle = distance(mouseXInitial, mouseYInitial, x, y) * 0.01;

    GLfloat axisX = dy;
    GLfloat axisY = -dx;
    GLfloat axisZ = 0.0;

    GLfloat length = distance(0.0, 0.0, axisX, axisY, axisZ);
    if (length!= 0.0) {
        axisX /= length;
        axisY /= length;
        axisZ /= length;
    }

    GLfloat tempMatrix[16];
    gluRotationMatrixARB(angle, axisX, axisY, axisZ);

    // 更新物体的旋转矩阵
    glMultMatrixf(tempMatrix);
    for (int i = 0; i < 16; i++) {
        rotationMatrix[i] = tempMatrix[i];
    }

    // 重新渲染场景以显示旋转后的效果
    glutPostRedraw();
}

通过以上步骤,在处理鼠标移动事件时,能够准确地计算出旋转轴和旋转角度,并更新物体的旋转矩阵,从而实现通过鼠标操作来旋转场景中的物体的效果。

标签:鼠标,OpenGL,int,0.0,矩阵,旋转,如何,GLfloat,trackball
From: https://www.cnblogs.com/DesertCactus/p/18537095

相关文章

  • 条件变量如何避免丢失通知
    在使用条件变量进行多线程编程时,确实存在丢失通知的风险,以下是一些可以避免丢失通知的方法:正确的等待条件设置原理:条件变量是基于特定条件来让线程等待或继续执行的。如果等待条件设置得不准确,可能会导致线程在不应该醒来的时候醒来,或者错过真正需要醒来的时机,从而出现通知丢......
  • 有DEM,如何在Global Mapper中绘制等高线,并导出至CAD
    通常,用无人机航测或其它途径得到的DEM、DSM来绘制等高线,一般流程是将DEM导出至南方CASS或其它格式的高程点文件,再用这些高程点来建立DTM、结三角网、编辑三角网,来进行等高线的绘制,做过等高线生产的测绘兄弟们都清楚,这个过程还是十分繁琐的。实际上,用GlobalMapper可以直接从DEM中......
  • 在C++中,条件变量的等待操作是如何实现的?
    在C++中,条件变量的等待操作主要通过std::condition_variable类来实现,其等待操作涉及到与互斥锁的配合使用,以下是详细的实现过程:包含必要的头文件首先需要包含<condition_variable>和头文件,因为条件变量std::condition_variable的使用需要与互斥锁(如std::mutex)协同工作,同时还......
  • 如何限制用户修改 long_query_time
    如何限制用户修改long_query_time需求来源数据库的long_query_time设置了写入慢查询日志的SQL语句执行时长的阈值,当应用系统修改为很小的值或0时,会在数据库的慢查询日志中记录大量SQL语句,导致数据库性能降低和占用磁盘空间的快速增长。GreatSQL对于影响整个数据库会......
  • 如何在原生鸿蒙APP中使用RN的bundle包
    一、创作背景上一篇博客中,我给大家分享了如何创建一个RN的项目,并且解决了其中的问题点,成功打出了Bundle包。接下来就是我给大家分享一下,如何在原生鸿蒙项目中使用那个Bundle包,这一篇分享完才算是开发环境真正的搭建好了。在本篇中,我将继续分享环境搭建中会遇到的坑点,帮助大家快......
  • 程序员 SEO 系列:如何找到更多搜索关键词?
    本文分享有效的关键词挖掘策略,帮助你识别低竞争、高流量的蓝海关键词,提升网站排名并带来持续流量增长。了解如何通过竞品分析、长尾词挖掘等方法,发掘适合你网站的关键词,快速提升SEO效果。一、关键词研究(挖词)的目的?SEO挖词的目的是通过深入Research和识别有流量潜力的关键词......
  • devc++配置opengl库
    由于VisualStudio太占内存,所以用老古董devc++配图形学的环境。用到的文件下载链接Step1:建项目首先打开dev点文件--新建--项目--Multimedia--OpenGLc++/c都行(我这里用的c++)名称最好用英文,然后确定,保存的地方也最好没有中文路径Step2:添加库文件找到DEV-C++的安装目录(右键......
  • DBeaver如何一次性执行多条sql语句,原来和单条不一样!
    前言我之前一直是用Navicat来连接数据库的,说实话,用起来真的很舒服。但是,后来,我离职了,换了一家新公司。新公司有一个规定,不准使用Navicat,其中的原因众所周知。由于Navicat是付费的,而公司又不想付这笔钱。而且,也不能使用破解的。于是,公司给我们推荐了DBeaver这款连接工具。好吧,有......
  • 如何对stm32的程序进行加密
    目的:保护单片机里的固件或代码不被读取出来通过代码配置 STM32的寄存器来实现读取保护(RDP)和调试接口锁定,可以采用以下方式:1.启用读取保护(RDP)可以在代码中配置RDP级别。例如在STM32F4系列中,RDP的设置存储在选项字节(OptionBytes)中。RDP的配置可以通过STM32HAL库......
  • 如何在 Linux 中按名称终止进程?
    在Linux系统中,进程是指正在执行的程序或任务的实例。每个程序在运行时会创建一个或多个进程,并且这些进程在后台或前台执行。虽然大部分进程是正常运行的,但有时候系统中可能会出现一些故障进程,这些进程可能会导致系统资源浪费或系统变得缓慢。在这种情况下,终止这些不正常的......