如何像CAD程序一样控制3D应用程序的视点
How to control 3D application viewpoint like CAD programs?
我正在使用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变量来改变注视点的高度
- 控制允许动态运行c++的并发操作数
- 从控制台中删除最后打印的元素
- 是否可以使用if constexpr删除控制流语句
- 无法在windows控制台中为C++程序提供必要的输入
- 如何将不同的可执行文件合并到一个窗口框架中进行编码?像浏览器一样
- 为什么在C++中对链表这样做?(像堆叠一样处理它们)
- 控制到达非空函数clang(-Wreturn-type)的末尾
- 堆栈和队列是否像C++中的数组一样传递?
- 查找 GCD:并非所有控制路径都返回值
- 通过 API 控制 DJI 相机
- 是否有技术原因阻止 Java 中的 final C++ 像 const 一样严格?
- 禁止在控制台上记录谷神星
- 是否可以使用一个类来控制 C++ 中另一个类的对象?(阿杜伊诺)
- 如何删除列出的"QGraphicsPathItem"对象以控制进程内存使用情况?
- 访问数据成员(本身是对象)的数据成员,就好像它们是类成员一样
- 我们可以将集合的值存储在变量中吗?就像我们可以将数组的值存储在变量中一样
- 我在 C++ 代码中遇到错误警告:控制到达非空函数 [-Wreturn 类型] 的末尾
- C++ Python 模块在 Blender 中崩溃,但在 Python 控制台中不会崩溃
- wx通用目录控制错误"wxTheFileIconsTable was nullptr"
- 如何像CAD程序一样控制3D应用程序的视点