如何像CAD程序一样控制3D应用程序的视点

How to control 3D application viewpoint like CAD programs?

本文关键字:控制 一样 3D 应用程序 视点 CAD 何像 程序      更新时间:2023-10-16

我正在使用OpenGL和SDL库编写3D应用程序。如何实现与CAD程序(如AutoCAD、FreeCAD或OpenSCAD)类似的相机控制?具体来说,我感兴趣的是通过在视口中点击和拖动鼠标来控制相机围绕某个点的无限制旋转,以及按预期行为的平移和缩放。

几年前,我遇到了一篇关于这个主题的文章,其中描述了一种优雅的方法。文章建议将鼠标位置投影到观察体内的一个半球上,然后应用一个等于先前投影位置、原点和当前投影位置形成的角度的旋转。

我记不起这篇文章的更多细节,也无法用谷歌找到它。

此外,这不是一个关于OpenGL基础或键盘/鼠标输入的问题。我目前正在使用FPS或飞行模拟启发的相机控制器。

文章建议将鼠标位置投影到观看体内的一个半球上,然后应用一个等于先前投影位置、原点和当前投影位置形成的角度的旋转。

通常被称为圆球控制,来自Ken Shoemake在Graphics Gems IV中的文章

这是一个旧的帖子,但我有代码,做你所要求的。我很久以前就想明白了。我刚刚用c++为一个新项目重写了它。它有助于在XZ平面上绘制网格来测试此代码。

基本上,你需要添加一些代码到keyDown和Keyup事件,这样你就可以从圆弧围绕中心(鼠标L键)和缩放(鼠标R键)和移动(Shift键)切换,你可以改变这是任何你想要的键代码。您还需要为鼠标事件Down、Move和UP添加代码。在这些事件中,您可以设置例程使用的bool值。我使用旧的gluLookAt来设置视图矩阵。如果你不想用它,你需要写一个。下面是设置视图矩阵的代码:

    void set_Eyes(void)
    {
        float sin_x, sin_y, cos_x, cos_y;
        sin_x = sin(z_rotation + angle_offset); // angle_offset is not needed...
        cos_x = cos(z_rotation + angle_offset); // It's a special use variable.
        cos_y = cos(x_rotation);
        sin_y = sin(x_rotation);
        cam_y = sin(x_rotation) * view_radius; // view_radius is always a negitive number.
        cam_x = (sin_x - (1.0f - cos_y) * sin_x) * view_radius;
        cam_z = (cos_x - (1.0f - cos_y) * cos_x) * view_radius;
        gluLookAt(
            cam_x + u_look_point_X, //eye positions
            cam_y + u_look_point_Y,
            cam_z + u_look_point_Z,
            u_look_point_X, // where we are looking
            u_look_point_Y,
            u_look_point_Z,
            0.0F, 1.0F, 0.0F); // up vector... Y is up
        eyeX = cam_x + u_look_point_X; // where the eye is in 3D space...
        eyeY = cam_y + u_look_point_Y; // needed for some shaders
        eyeZ = cam_z + u_look_point_Z; // u_look_points is the point we are looking at.
    }

下面是做数学运算的代码。为了能够在移动鼠标的方向上移动观看点,无论你在Y轴上观看的角度是什么,你都必须对移动值进行一些2D旋转计算。数学并不复杂,但理解我的代码可能很复杂。

//=================================================
// Mouse Movement Control
// "ArcBall" Style of viewing and movement.
//
//
//=================================================

#include "stdafx.h"
#include "mouse_control.h"
using namespace std;
// external variables
extern float PI;// 3.141592654
extern bool Right_Mouse_Down;
extern bool Left_Mouse_Down;
//==================================================
float m_speed = 2.0; // control mouse movement speed
float m_speed_global = 2.0; // Move to external setting
//==================================================
// Keep z_rotaion in -PI*2 to PI*2 range
float check_overflow(float v)
{
    if (v > 0.0f) if (v > (PI * 2)) v -= (PI * 2);
    if (v < 0.0f) if (v < (-PI * 2)) v += (PI * 2);
    return v;
}
// Keep x_rotaion in 0.0 to -PI/2.0 range
float check_overflow_x(float v)
{
    const float Half_PI = PI / 2.0f;
    if (v < 0.0f)
        if (v < -Half_PI+0.001f) // need the +0.001f to avoide fail at set_eyes function
            v = -Half_PI+0.001f;
    if (v > 0.0f) v = 0.0f;
    return v;
}
// handle mouse rotation
void handle_mouse_eye_rotaion(CPoint point)
{
    CPoint delta = mouse_p - point;
    int deadzone = 0;
    m_speed = m_speed_global * -view_radius * 0.1f;
    if (xz_translation_flag || Left_Mouse_Down)
    {
        // about z
        if (delta.x < deadzone)
        {
            rotate_left(delta.x);
        }
        if (delta.x > deadzone)
        {
            rotate_right(delta.x);
        }
        // about x
        if (delta.y < deadzone)
        {
            rotate_down(delta.y);
        }
        if (delta.y > deadzone)
        {
            rotate_up(delta.y);
        }
        mouse_p = point;
    }
    else
    {
        if (Right_Mouse_Down && !y_move_flag)
        {
            zoom_radius(delta); // change zoom
            mouse_p = point;
        }
    }
}
// rotate view clockwise
void rotate_left(int x)
{
    if (x > 100) x = 100;
    if (x < -100) x = -100;
    float t = (float(x) / (100.0f * PI));
    if (!xz_translation_flag)
    {
        z_rotation += t * m_speed_global;
        z_rotation = check_overflow(z_rotation);
        return;
    }
    u_look_point_X -= (t * cosf(z_rotation)*2.0f * m_speed);
    u_look_point_Z += (t * sinf(z_rotation)*2.0f * m_speed);
}
// rotate view counter clockwise
void rotate_right(int x)
{
    if (x > 100) x = 100;
    if (x < -100) x = -100;
    float t = (float(x) / (100.0f * PI));
    if (!xz_translation_flag)
    {
        z_rotation += t * m_speed_global;
        z_rotation = check_overflow(z_rotation);
        return;
    }
    u_look_point_X -= (t * cosf(z_rotation)*2.0f * m_speed);
    u_look_point_Z += (t * sinf(z_rotation)*2.0f * m_speed);
}
//======================== Y
// rotate view up
void rotate_up(int x)
{
    if (x > 100) x = 100;
    if (x < -100) x = -100;
    float t = (float(x) / (100.0f * PI));
    if (!xz_translation_flag)
    {
        x_rotation += t * m_speed_global;
        x_rotation = check_overflow_x(x_rotation);
        return;
    }
    u_look_point_Z -= (t * cosf(z_rotation)*2.0f * m_speed);
    u_look_point_X -= (t * sinf(z_rotation)*2.0f * m_speed);
}
// rotate view down
void rotate_down(int x)
{
    if (x > 100) x = 100;
    if (x < -100) x = -100;
    float t = (float(x) / (100.0f * PI ));
    if (!xz_translation_flag)
    {
        x_rotation += t * m_speed_global;
        x_rotation = check_overflow_x(x_rotation);
        return;
    }
    u_look_point_Z -= (t * cosf(z_rotation)*2.0f * m_speed);
    u_look_point_X -= (t * sinf(z_rotation)*2.0f * m_speed);
}
// used to change zoom
void zoom_radius(CPoint delta)
{
    if (delta.y > 0)
    {
        if (delta.y > 100) delta.y = 100;
    }
    if (delta.y < 0)
    {
        if (delta.x < -100) delta.x = -100;
    }
    float y = float(delta.y) / 100.0f;
    view_radius += y* 2.0f * m_speed;
    // Adjust these to change max zoom in and out values.
    // view_radius MOST STAY A NEGATIVE NUMBER!
    // OpenGL ALWAYS LOOKS IN THE NEGATIVE Z SCREEN DIRECTION!
    if (view_radius > -0.5f) view_radius = -0.5f;
    if (view_radius < -1000.0f) view_radius = -1000.0f;
}
// debug junk
//string s;
//s = "mouse_p";
//outStr1(s, mouse_p.x, mouse_p.y);
//s = "delta";
//outStr1(s, delta.x, delta.y);
// debug code
void outStr1(string s, int a, int b)
{
    char buf[100];
    sprintf_s(buf, "%s : A= %i : B= %in", s.c_str(), a, b);
    OutputDebugStringA(buf);
    return;
}

注:"mouse_p"需要在按下鼠标左键或右键时设置。它用来获取鼠标所在位置到鼠标所在位置的增量距离。

"point"是鼠标移动事件中的鼠标位置。mouse_p在计算完成后被设置为匹配"point"。否则移动会开始失去控制!

按键弹起…将bool设置为false (xz_translation_flag和y_move_flag)。KEYDOWN…当按下shift键时设置xz_translation_flag = TRUE。Y_move_flag还没有直接绑定的函数。它将被用来移动u_look_point_Y变量来改变注视点的高度