GLUT Trackball Demo

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: GLUT Trackball Demo eryar@163.com 1.Introduction 在三维场景中建立模型后,为了方便用户从各个角度观察模型,从而需要对三维视图进行控制。常见的视图交互控制方式有:Trackball控制器、飞行控制器,还有三维游戏常用的第一人称控制器。

GLUT Trackball Demo

eryar@163.com

1.Introduction

在三维场景中建立模型后,为了方便用户从各个角度观察模型,从而需要对三维视图进行控制。常见的视图交互控制方式有:Trackball控制器、飞行控制器,还有三维游戏常用的第一人称控制器。这些视图控制器的根本是对模型视图矩阵MODELVIEW进行变换。

Trackball控制器以一种用户友好的交互方式来变换视图,原理是由Trackball激发,Trackball如下图所示:

wpsD30B.tmp

Figure 1. Trackball

通过手指在球面上滚动,就可以对三维视图进行控制。现在需要用鼠标的拖动来模拟Trackball以实现对三维视图的控制。在OpenGL中实现Trackball控制视图分为以下几步:

1.将鼠标移动时的屏幕坐标点映射到单位球上;
wpsD32B.tmp

2.将开始旋转视图时鼠标点到球心的向量与鼠标移动过程中的坐标点球心的向量叉乘,即可得到旋转轴;

wpsD32C.tmp
根据叉乘的定义,可以得到旋转角度:
wpsD32D.tmp

有了旋转轴和旋转角度,就可以对视图进行旋转操作了。

2.GLUT Test

为了简明地说明Trackball的原理,这里只使用了GLUT库和OpenCASCADE中的四元数和向量相关的类。如果其他开源库也有向量计算和四元数据计算类,也可以将代码很快移植到使用其他库,如矩阵计算库Eigen等。下面给出GLUT的示例代码:

/*
Copyright(C) 2017 Shing Liu(eryar@163.com)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files(the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/


#include <gp_XYZ.hxx>
#include <gp_Trsf.hxx>
#include <gp_Quaternion.hxx>

#include <gl/glut.h>

#pragma comment(lib, "TKernel.lib")
#pragma comment(lib, "TKMath.lib")

GLint VIEWPORT_WIDTH = 0;
GLint VIEWPORT_HEIGHT = 0;

gp_XYZ U;
gp_XYZ V;

gp_Quaternion R;
gp_Quaternion Q;


void init( void)
{
    GLfloat aSpecularMaterial[]  = {1.0f, 1.0f, 1.0f, 1.0f};
    GLfloat aLightPosition[] = {1.0, 1.0, 1.0, 0.0};
    GLfloat aWhiteLight[] = {1.0, 1.0, 1.0, 1.0};
    GLfloat aModelAmbient[] = {0.1, 0.1, 0.1, 1.0};

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glShadeModel(GL_SMOOTH);

    glMaterialfv(GL_FRONT, GL_SPECULAR, aSpecularMaterial);
    glMaterialf(GL_FRONT, GL_SHININESS, 60.0);

    glLightfv(GL_LIGHT0, GL_POSITION, aLightPosition);
    glLightfv(GL_LIGHT0, GL_SPECULAR, aWhiteLight);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, aWhiteLight);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, aModelAmbient);

     //  Enable lighting
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_DEPTH_TEST);
}

void display( void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glutSolidTeapot(1.0);

     //  draw mouse motion point.
    glBegin(GL_LINES);
        glVertex3f(0.0, 0.0, 0.0);
        glVertex3f(U.X() * 2.0, U.Y() * 2.0, U.Z() * 2.0);
    glEnd();

    glutSwapBuffers();
}

void reshape(GLint theWidth, GLint theHeight)
{
    VIEWPORT_WIDTH = theWidth;
    VIEWPORT_HEIGHT = theHeight;

     //  Reset viewport and projection parameter
    glViewport(0, 0, theWidth, theHeight);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

     if (theWidth <= theHeight)
    {
        glOrtho(-1.5, 1.5, -1.5 * theHeight / theWidth, 1.5 * theHeight / theWidth, -10.0, 10.0);
    }
     else
    {
        glOrtho(-1.5 * theWidth / theHeight, 1.5 * theWidth / theHeight, -1.5, 1.5, -10.0, 10.0);
    }

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

void mapToSphere(GLint theX, GLint theY, gp_XYZ& thePnt)
{
    GLfloat aX = (theX - 0.5 * VIEWPORT_WIDTH) / VIEWPORT_WIDTH;
    GLfloat aY = (0.5 * VIEWPORT_HEIGHT - theY) / VIEWPORT_HEIGHT;

    GLfloat aSinx = sin(M_PI * aX * 0.5);
    GLfloat aSiny = sin(M_PI * aY * 0.5);
    GLfloat aSxy2 = aSinx * aSinx + aSiny * aSiny;

    thePnt.SetX(aSinx);
    thePnt.SetY(aSiny);
    thePnt.SetZ(aSxy2 < 1.0 ? sqrt(1.0 - aSxy2) : 0.0);

}

void mouse(GLint theButton, GLint theState, GLint theX, GLint theY)
{
    mapToSphere(theX, theY, U);

    glutPostRedisplay();
}

void motion(GLint theX, GLint theY)
{
    mapToSphere(theX, theY, V);

    gp_XYZ W = U.Crossed(V);
     if (W.Modulus() < gp::Resolution())
    {
         return;
    }

    GLfloat aAngle = W.Modulus() / (U.Modulus() * V.Modulus());
    aAngle = asin(aAngle);

    glRotatef(aAngle * 180.0 / M_PI, W.X(), W.Y(), W.Z());

    glutPostRedisplay();

    U = V;
}

int main( int argc,  char* argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
    glutInitWindowSize(500, 300);
    glutCreateWindow("Trackball Demo");

    init();

    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutMouseFunc(mouse);
    glutMotionFunc(motion);

    glutMainLoop();

     return 0;
}

上述程序运行结果如下动图所示:

trackball1

从上图可知,当旋转几次后视图并没有得到预期的结果。因为程序将鼠标映射后坐标与球心得到的向量进行了显示,发现当旋转几次后,这个向量并没有跟随鼠标。

3.Transform

通过观察上面代码程序运行的结果,可以发现鼠标映射函数得到的映射点始终是位于XOY平面上的一个半球面上。当视图被旋转后,视图的坐标系已经发生了变化,而映射点并没有。为了跟踪这个变换用四元数进行累乘来记录这一系列的旋转变换。最后在映射函数中将映射点变换到已经改变的视图坐标系中。

即在鼠标移动处理函数中增加记录变换:

    gp_Quaternion q(W, aAngle);
    R.Multiply(q);

在mapToSphere函数中增加:

gp_Trsf aTrsf;
aTrsf.SetRotation(Q.Inverted());
aTrsf.Transforms(thePnt);

列出升级后的全部代码如下所示:

/*
Copyright(C) 2017 Shing Liu(eryar@163.com)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files(the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/


#include <gp_XYZ.hxx>
#include <gp_Trsf.hxx>
#include <gp_Quaternion.hxx>

#include <gl/glut.h>

#pragma comment(lib, "TKernel.lib")
#pragma comment(lib, "TKMath.lib")

GLint VIEWPORT_WIDTH = 0;
GLint VIEWPORT_HEIGHT = 0;

gp_XYZ U;
gp_XYZ V;

gp_Quaternion R;
gp_Quaternion Q;


void init( void)
{
    GLfloat aSpecularMaterial[]  = {1.0f, 1.0f, 1.0f, 1.0f};
    GLfloat aLightPosition[] = {1.0, 1.0, 1.0, 0.0};
    GLfloat aWhiteLight[] = {1.0, 1.0, 1.0, 1.0};
    GLfloat aModelAmbient[] = {0.1, 0.1, 0.1, 1.0};

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glShadeModel(GL_SMOOTH);

    glMaterialfv(GL_FRONT, GL_SPECULAR, aSpecularMaterial);
    glMaterialf(GL_FRONT, GL_SHININESS, 60.0);

    glLightfv(GL_LIGHT0, GL_POSITION, aLightPosition);
    glLightfv(GL_LIGHT0, GL_SPECULAR, aWhiteLight);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, aWhiteLight);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, aModelAmbient);

     //  Enable lighting
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_DEPTH_TEST);
}

void display( void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glutSolidTeapot(1.0);

     //  draw mouse motion point.
    glBegin(GL_LINES);
        glVertex3f(0.0, 0.0, 0.0);
        glVertex3f(U.X() * 2.0, U.Y() * 2.0, U.Z() * 2.0);
    glEnd();

    glutSwapBuffers();
}

void reshape(GLint theWidth, GLint theHeight)
{
    VIEWPORT_WIDTH = theWidth;
    VIEWPORT_HEIGHT = theHeight;

     //  Reset viewport and projection parameter
    glViewport(0, 0, theWidth, theHeight);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

     if (theWidth <= theHeight)
    {
        glOrtho(-1.5, 1.5, -1.5 * theHeight / theWidth, 1.5 * theHeight / theWidth, -10.0, 10.0);
    }
     else
    {
        glOrtho(-1.5 * theWidth / theHeight, 1.5 * theWidth / theHeight, -1.5, 1.5, -10.0, 10.0);
    }

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

void mapToSphere(GLint theX, GLint theY, gp_XYZ& thePnt)
{
    GLfloat aX = (theX - 0.5 * VIEWPORT_WIDTH) / VIEWPORT_WIDTH;
    GLfloat aY = (0.5 * VIEWPORT_HEIGHT - theY) / VIEWPORT_HEIGHT;

    GLfloat aSinx = sin(M_PI * aX * 0.5);
    GLfloat aSiny = sin(M_PI * aY * 0.5);
    GLfloat aSxy2 = aSinx * aSinx + aSiny * aSiny;

    thePnt.SetX(aSinx);
    thePnt.SetY(aSiny);
    thePnt.SetZ(aSxy2 < 1.0 ? sqrt(1.0 - aSxy2) : 0.0);

    gp_Trsf aTrsf;
    aTrsf.SetRotation(Q.Inverted());
    aTrsf.Transforms(thePnt);
}

void mouse(GLint theButton, GLint theState, GLint theX, GLint theY)
{
    mapToSphere(theX, theY, U);

    Q = R;

    glutPostRedisplay();
}

void motion(GLint theX, GLint theY)
{
    mapToSphere(theX, theY, V);

    gp_XYZ W = U.Crossed(V);
     if (W.Modulus() < gp::Resolution())
    {
         return;
    }

    GLfloat aAngle = W.Modulus() / (U.Modulus() * V.Modulus());
    aAngle = asin(aAngle);

    glRotatef(aAngle * 180.0 / M_PI, W.X(), W.Y(), W.Z());

    glutPostRedisplay();

    gp_Quaternion q(W, aAngle);
    R.Multiply(q);

    U = V;
}

int main( int argc,  char* argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
    glutInitWindowSize(500, 300);
    glutCreateWindow("Trackball Demo");

    init();

    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutMouseFunc(mouse);
    glutMotionFunc(motion);

    glutMainLoop();

     return 0;
}

这次程序运行和预期结果一致,旋转很流畅:

trackball2

4.Conclusion

程序员总是有很强的控制欲,希望一切尽在掌握之中。在三维场景中建立模型后,如何对视图进行控制来方便地观察模型呢?最常见的控制方式就是Trackball. OpenSceneGraph、Eigen等开源库都有相关的实现。

Trackball的实现主要是将鼠标点映射到一个球面上,然后使用叉乘得到旋转轴和旋转角度。为了旋转的流畅,使用四元数记录了一系列的旋转变换,最后通过将映射点进行坐标变换得到满意的效果。

5.References

1. Virtual Trackball. http://gukewen.sdu.edu.cn/panrj/courses/4-AngelCGE2-Virtual-Trackball.pdf

2. Object Mouse Trackball https://www.khronos.org/opengl/wiki/Object_Mouse_Trackball

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
7月前
|
C语言 计算机视觉 Python
【Qt】Qt下配置OpenCV
【Qt】Qt下配置OpenCV
|
计算机视觉
成功解决cv2.error: OpenCV(4.1.2) C:\projects\opencv-python\opencv\modules\imgproc\src\color.cpp:182: err
成功解决cv2.error: OpenCV(4.1.2) C:\projects\opencv-python\opencv\modules\imgproc\src\color.cpp:182: err
编译错误:GL/glew.h, GL/glut.h, EGL/egl.h没有那个文件或目录
编译错误:GL/glew.h, GL/glut.h, EGL/egl.h没有那个文件或目录
319 0
Qt&Vtk-021-HelloWorld
Qt&Vtk-021-HelloWorld
135 0
Qt&Vtk-021-HelloWorld
Qt&Vtk-023-MultiView
Qt&Vtk-023-MultiView
145 0
Qt&Vtk-023-MultiView
Qt&Vtk-013-Cube
Qt&Vtk-013-Cube
154 0
Qt&Vtk-013-Cube
Qt&Vtk-030-Theme
Qt&Vtk-030-Theme
130 0
Qt&Vtk-030-Theme
Qt&Vtk-029-SpecularSpheres
Qt&Vtk-029-SpecularSpheres
125 0
Qt&Vtk-029-SpecularSpheres
Qt-QML-Charts-ChartView-编译错误-ASSERT: “!“No style available without QApplication!
昨天本来是回家想好好琢磨一下使用Chart来绘制曲线的,奈何在建立项目的时候也就卡住了,加上心情比较烦躁,也没有耐心寻找答案就草草了事。所以今天继续搞定这个。
357 0
Qt-QML-Charts-ChartView-编译错误-ASSERT: “!“No style available without QApplication!