计算机图形:图形GUI、交互输入

图形数据的输入

图形APP支持多种输入数据,如坐标位置值、属性值、字符串、几何变换的参数、照明参数等.

输入的过程需要窗口和特殊硬件设备的交互.

逻辑输入设备

按输入的数据类型将输入功能分类,用来输入某种特定数据的设备称为这种数据类型的逻辑输入设备(Logical Input Device).

标准的逻辑输入设备:

  • LOCATOR 指定坐标位置(x,y)的设备(定位设备)
  • STROKE 指定一组坐标位置的设备(笔划设备)
  • STRING 输入文字的设备(字符串设备)
  • VALUATOR 指定标量值的设备(定值设备)
  • CHOICE 选择菜单项的设备(选择设备)
  • PICK 选择图形的组成部分的设备(拾取设备)

tips: 一种物理设备可用作多种逻辑输入设备,并非一对一关系.

  • 定位设备

屏幕光标定位是交互式选择坐标位置的标准方法.

典型物理设备:键盘、鼠标、触摸板、游戏杆、拇指轮、数字化触笔、手动光标及其他光标定位设备.

键盘有多种方式做定位设备:
4个控制键,将光标向上、向下、向左、向右移动;
4个额外的键,将光标沿对角线移动;
持续按下光标控制键,可以快速移动光标.

也可用键盘、光笔直接输入精准的坐标位置.

  • 笔划设备

用于输入一组顺序的坐标点. 相当于多次调用定位设备,因此定位设备通常可用作笔划设备.

  • 字符串设备

典型物理设备:键盘. 定位或笔划设备也可以逐步绘制字符.

  • 定值设备

用于输入标量值,设定各种图形参数,如几何变换参数、观察参数、光照参数等.

典型物理设备:控制旋钮、(带数字的)键盘、游戏杆、数据版.
虚拟设备:滑动块、按钮、菜单等.

  • 选择设备

常用菜单选择设计选项、参数值.

典型物理设备:光标定位设备,如鼠标、键盘、触摸板等,用于选择菜单.

还有一种特别的选择设备:语音输入,在选项数较少(<20)时较有用.

  • 拾取设备

用于选择场景中即将进行变换或编辑的部分. 任何以拾取为目标的输入机械设备,都可以为归为拾取设备.

典型物理设备:屏幕光标定位的设备,如鼠标、游戏杆、键盘等.

光标定位方法,如何拾取指定对象呢?
需要针对当前场景,进行逆观察、几何变换,将选中的屏幕坐标映射到场景的世界坐标,然后根据世界坐标位置与场景中各对象坐标范围比较. 如果某个对象的坐标范围包含该拾取位置,则拾取该对象.
如果同时有多个对象满足要求,则需要根据具体场景进一步确定.

输入功能

图形软件包通常提供多种函数供用户选择输入设备、数据类型,允许用户指定选项:
1)图形APP、设备如何交互,即输入模式.
2)使用哪种物理设备为特定逻辑分类提供输入.
3)何时输入数据.

  • 输入模式

指明程序如何与输入设备交互. 输入模式有:

1)请求模式(request mode)由APP启动数据输入. 从提出请求时被挂起,直到接收了所要求的数据.
2)取样模式(sample mode)APP和输入设备各自独立地操作. 输入设备每次得到的新数据覆盖旧数据,当程序请求数据时,就从输入设备获取当前值.
3)事件模式(event mode)输入设备启动数据输入,并交给APP. 输入设备将数据放进一个输入队列(事件队列),所有输入数据被存储. APP需要数据时,从中取出.

  • 回显反馈

用户有时要求回显输入的数据、参数,便于判定是否输入成功. 此时,输入数据显示在一个指定的屏幕区域内.

  • 回调函数

回调函数是图形软件包实现设备无关的重要技术. 回调函数与系统进行交互,指定当某个输入事件发生时,程序采取何种行为.
系统不需要知道用户程序的细节,只提供何时调用用户程序的接口.

交互式构图技术

有多种构图交互技术:定位对象位置、调整对象尺寸、应用约束、设计形状和表面图案.

定位

通过指定设备交互选择坐标位置,通常是定位屏幕光标.

如何使用定位的位置,取决于设定的处理选项,如作为线段一个端点,定位对象,作为物体的中心等.

拖拽

拖拽用于移动对象位置.

过程通常是这样的:先用鼠标将光标定位到一个对象,然后按下鼠标键将光标移动到新位置,松开鼠标选择的对象就会移动到新位置.

约束

有些对象的方向、对齐方式需要预先说明,而约束正是干这个的. 约束是改变输入坐标值,以产生对象坐标的指定方向、对齐方式的规则.

网格

正交直线的网格是屏幕上的另一种约束. 任何输入坐标位置将移动到最近的两根网格线的交点上. 网格可以选择显示,也可以不显示.

橡皮条方法

通过在起始点到移动的屏幕光标之间拉出一条直线的橡皮条方法,可以构造和定位直线段. 橡皮条方法可以使一个对象的尺寸被交互地拉伸或收缩.

例如,先按下鼠标,屏幕光标位置作为直线段的第一个端点;然后光标移动,就会绘制橡皮条线;直到鼠标松开,完成一条直线段的输入.

除直线段,还能构造、定位其他对象,如矩形、圆等.

引力场

构造图形时,有时需要在线段端点连接其他线段. 由于很难通过屏幕光标精准定位线段,因此将任意一个靠近线段的点位置转换成线段上的位置. 而转换通过在线段附近建立引力场(gravity field)实现.

在引力场范围内的任意位置,均被“吸引”到线段上最靠近该位置的点.

可通过放大端点的引力场区域,方便用户更容易在端点连接线段.

tips: 应力场不能太大,以免跟其他线段的重叠;也不能太小,不能起到定位效果.

OpenGL交互输入

因为需要与窗口系统连接,所以交换设备输入由GLUT子程序处理.

GLUT有从标准输入设备(鼠标、键盘、数据板、空间球、按钮盒等)接受输入的函数.

GLUT鼠标函数

  • 鼠标点击函数

注册一个鼠标按钮按下或松开时调用的函数.

tips: 前提条件是屏幕光标在窗口内.

glutMouseFunc(mouseFcn);

回调函数mouseFcn由用户定义,由系统调用:

void mouseFcn(GLint button, GLint action, GLint xMouse, GLint yMouse);

button可为三个符号常量:
1)GLUT_LEFT_BUTTON 表示鼠标左键;
2)GLUT_MIDDLE_BUTTON 表示鼠标中键;
3)GLUT_RIGHT_BUTTON 表示鼠标右键.

action代表事件,值为GLUT_DOWN或GLUT_UP,表示鼠标发生按下或松开事件.

系统调用mouseFcn时,会将鼠标光标在窗口的当前位置(xMouse, yMouse)传入. 该位置相对于窗口左上角,即原点在左上角,xMouse是光标到窗口左边界距离,yMouse到右边界距离.

  • 鼠标拖动函数(按下)

当鼠标在窗口内移动,并且一个或多个鼠标按钮被激活时,调用该函数.

glutMotionFunc(fcnDoSth);

fcnDoSth由用户定义,原型为:

void fcnDoSth(GLint xMouse, GLint yMouse);

(xMouse, yMouse)是当鼠标移动且按钮按下时,光标相对于窗口左上角的位置.

  • 鼠标拖动函数(未按下)

当鼠标在窗口内移动但未被按下时调用.

glutPassiveMotionFunc(fcnDoSthElse);

fcnDoSthElse类似上面的fcnDoSth.

GLUT键盘函数

  • 普通按键

当键盘上一个按键被按下时调用

回调注册:

glutKeyboardFunc(keyFcn);

keyFcn由用户定义,原型:

void keyFcn(GLubyte key, GLint xMouse, GLint yMouse);

key是一个字符值或对应的ASCII编码,(xMouse, yMouse)是鼠标光标在窗口内的坐标位置,原点在窗口左上角.

  • 功能键、方向键、特殊键

这类按键按下时调用

glutSpecialFunc(specialKeyFcn);

specialKeyFcn由用户定义,原型:

void specialKeyFcn(GLint specialKey, GLint xMouse, GLint yMouse);

specialKey取值:
功能键,符号常量GLUT_KEY_F1~F12;
方向键,符号常量GLUT_KEY_UP/DOWN/LEFT/RIGHT;
其他特殊按键,翻页GLUT_KEY_PAGE_UP/DOWN,首尾GLUT_KEY_HOME/END,插入GLUT_KEY_INSERT;
“backspace”“delete” Esc键,ASCII编码规定,分别为8、127、27.

GLUT数据板函数

  • 输入按钮事件
// 注册回调
glutTableButtonFunc(tableFcn);
// 用户定义, 原型
void tableFcn(GLint tableButton, GLint action, GLint xTablet, GLint yTablet);

tableButton整型标识符,如1,2,3等,用于标识按钮. action表示按钮行为,取值GLUT_UP/GLUT_DOWN. (xTablet, yTable)数据板坐标.

  • 查询数据板有效按钮数

返回有效按钮数目

glutDeviceGet(GLUT_NUM_TABLE_BUTTONS);
  • 数据板光笔或光标的移动
// 注册回调
glutTableMotionFunc(tableMotionFcn);
// 用户定义, 原型
void tableMotionFcn(GLint xTablet, GLint yTablet);

OpenGL拾取操作

基本思想:鼠标点击时,用一个基于光标的拾取窗口(矩形)在窗口中拾取对象,一次可能拾取到多个对象. 每次拾取选中的对象,然后向拾取缓冲区添加一个记录,记录包含该对象在名称栈以下所有对象的名称(名称列表).

这种类型记录,也称为点击记录. 记录的数目取决于拾取窗口的尺寸和位置,例如拾取窗口较大,则同时选中的对象可能更多.

为使用OpenGL拾取(也叫选择)功能,APP须包含以下过程:

  • 创建、显示一个场景;
  • 拾取一个屏幕位置,并在鼠标回调函数内进行操作:

1)glSelectBuffer()设置一个拾取缓冲区

2)glRenderMode(GL_SELECT)激活拾取操作,进入选择模式

3)glInitNames()为对象标识符初始化一个ID名称栈

4)定义用于选择的视景体,glPushMatrix()保存当前的观察和几何变换状态

5)gluPickMatrix()指定拾取窗口

6)glPushName()给对象分配标识符,然后用观察体再处理一次场景,从而将拾取信息存储到拾取缓冲区中

7)glPopMatrix()恢复原来的观察和几何变换状态

8)glRenderMode(GL_RENDER)确定被拾取的对象的数目,然后返回正常的绘制模式

9)处理拾取信息

记录包含信息:

  1. 对象在名称栈中的位置,即名称栈中位于该对象之下(含该对象)的名称数目;
  2. 拾取对象的最小深度(拾取对象窗口坐标z值);
  3. 拾取对象的最大深度;
  4. 从名称栈中第一个名称(底部)到拾取对象之间所有名称的列表(代表当前所有拾取的对象).

tips: 一条记录可能包含多个拾取对象的名称.

tips: 最小深度、最大深度范围[0, 1],然后乘以\(2^{32}-1\),四舍五入到最接近的无符号整数,结果存放到记录中.

glRenderMode的第三个参数GL_FEEDBACK,即反馈模式,不显示对象,而是将对象的坐标及其他信息存储到一个反馈缓冲区中. glRenderMode返回当前模式下对应数组存储的记录个数.

名称栈操作:
glPushName(ID),glPopName()能对名称栈进行压栈、弹栈操作,glLoadName(ID)替换栈顶元素.

gluPickMatrix定义选定视区内的拾取窗口:

gluPickMatrix(xPick, yPick, widthPick, heightPick, vpArray);

拾取窗口(矩形)左下角相对于视区(原点在左下角)的坐标(xPick, yPick),宽、高分别为widthPick、heightPick. 而光标坐标(xMouse, yMouse)是相对于视区左上角,因此要翻转yMouse值(视区高度 - yMouse).

示例:鼠标点击拾取窗口中红、绿、蓝三个矩形

完整代码 参见pickup_window.cpp | gitee

OpenGL菜单

支持简单的弹出式菜单.

创建GLUT菜单

  • 创建一个弹出式菜单
glutCreateMenu(menuFcn);

回调函数menuFcn由用户定义

void menuFcn(GLint menuItemNumber);

传递给menuItemNumber的值用于执行某些操作. 当一个菜单被创建时,被关联到当前窗口.

  • 添加菜单项

菜单上显示的每一项内容,称为菜单项. 需要显示一项内容时,就要添加一个菜单项.

glutAddMenuEntry(charString, menuItemNumber);

charString: 菜单项显示的文字;menuItemNumber菜单项在菜单上的位置. 选择某个菜单项时,menuFcn会被调用,对应menuItemNumber会被传给menuFcn对应参数.

例,创建一个有2个菜单项的菜单

glutCreateMenu(menuFcn);

glutAddMenuEntry("First Menu Item", 1);
glutAddMenuEntry("Second Menu Item", 2);
  • 为菜单项选择鼠标键

设置点击鼠标哪个键触发弹出式菜单. 创建菜单的位置取决于鼠标点击事件位置.

glutAttachMenu(button);

button值可为LEFT(GLUT_LEFT_BUTTON)、MIDDLE、RIGHT,分别代表鼠标左键、中箭、右键.

创建、管理多个GLUT菜单

  • 菜单标识符

创建菜单时,菜单会被关联到当前窗口. 一个窗口可以创建多个菜单,不同窗口可以创建不同菜单. 如何区分不同的菜单?

创建菜单时,可为其分配一个整型标识符,从1开始顺序编号.

menuID = glutCreateMenu(menuFcn);

返回值就是标识符. 而最新创建的菜单,成为当前窗口的当前菜单(current menu).

  • 激活指定菜单为当前菜单
glutSetMenu(menuID);
  • 清除指定菜单

如果清除的是当前菜单,那么当前窗口就没有当前菜单了

glutDestroyMenu(menuID);
  • 获取当前菜单标识符
currentMenuID = glutGetMenu();

如果当前窗口没有菜单或已被清除,那么返回0.

创建GLUT子菜单

可将一个子菜单关联到一个菜单上.

// 创建子菜单
submenuID = glutCreateMenu(submenuFcn);
glutAddMenuRetry("First Submenu Item", 1);

glutCreateMenu(memFcn);
glutAddMenuEntry("First Menu Item", 1);
...
// 将子菜单添加到主菜单
glutAddSubMenu("Submenu Option", submenuID);

修改GLUT菜单

  • 修改与菜单关联的鼠标键

先glutDetachMenu删除,后glutAttachMenu关联新的鼠标键. 删除关联的鼠标键:

glutDetachMenu(mouseButton);

mouseButton是代表鼠标键(左、中右)GLUT常量.

  • 删除菜单项

修改菜单项也是需要先删除后添加.

glutRemoveMenuItem(itemNuber);

itemNuber是待删除菜单项的标识符.

小结

  • 介绍了图形APP的多种物理设备(硬件设备),对其进行逻辑分类,图形输入API可设计成设备无关.

  • 6类逻辑设备:定位设备、笔划设备、字符设备、定值设备、选择设备、拾取设备.

  • 图形软件包的输入函数可定义成3种输入模式:
    请求模式:将输入的控制交给APP;
    取样模式:设备与程序并行工作;
    事件模式:设备启动数据输入,控制数据的处理.

  • 网格、引力场、橡皮条方法常用来帮助用户定位、构图.

  • GUI设计指导思想:易用、清晰、灵活. 应尽量减少用户记忆要求. 提供足够反馈,一定的回退和错误处理能力.

  • GLUT工具包提供鼠标、键盘等输入函数,提供简单的菜单、子菜单功能.

posted @ 2024-01-02 11:04  明明1109  阅读(74)  评论(0编辑  收藏  举报