Python实现3D建模工具(下)

Python实现3D建模⼯具(下)
⽤户接⼝
我们希望与场景实现两种交互,⼀种是你可以操纵场景从⽽能够从不同的⾓度观察模型,⼀种是你拥有添加与操作修改模型对象的能⼒。为了实现交互,我们需要得到键盘与⿏标的输⼊,GLUT允许我们在键盘或⿏标事件上注册对应的回调函数。
新建interaction.py⽂件,⽤户接⼝在Interaction类中实现。
导⼊需要的库
from collections import defaultdict
模糊综合评价法
from OpenGL.GLUT import glutGet, glutKeyboardFunc, glutMotionFunc, glutMouseFunc, glutPassiveMotionFunc, \ glutPostRedisplay, glutSpecialFunc
from OpenGL.GLUT import GLUT_LEFT_BUTTON, GLUT_RIGHT_BUTTON, GLUT_MIDDLE_BUTTON, \
GLUT_WINDOW_HEIGHT, GLUT_WINDOW_WIDTH, \
GLUT_DOWN, GLUT_KEY_UP, GLUT_KEY_DOWN, GLUT_KEY_LEFT, GLUT_KEY_RIGHT
import trackball
初始化Interaction类,注册glut的事件回调函数。
class Interaction(object):
def__init__(self):
""" 处理⽤户接⼝ """
#被按下的键
self.pressed = None
#轨迹球,会在之后进⾏说明
#当前⿏标位置
#回调函数词典
self.callbacks = defaultdict(list)
def register(self):
""" 注册glut的事件回调函数 """
glutMouseFunc(self.handle_mouse_button)
glutMotionFunc(self.handle_mouse_move)
glutKeyboardFunc(self.handle_keystroke)
英德市卫生局glutSpecialFunc(self.handle_keystroke)
2012江苏高考英语回调函数的实现:
def handle_mouse_button(self, button, mode, x, y):
""" 当⿏标按键被点击或者释放的时候调⽤ """
xSize, ySize = glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT) y = ySize - y # OpenGL原点在窗⼝左下⾓,窗⼝原点在左上⾓,所以需要这种转换。
if mode == GLUT_DOWN:
#⿏标按键按下的时候
self.pressed = button
if button == GLUT_RIGHT_BUTTON:
pass
elif button == GLUT_LEFT_BUTTON:
else: # ⿏标按键被释放的时候
self.pressed = None
#标记当前窗⼝需要重新绘制
glutPostRedisplay()
def handle_mouse_move(self, x, screen_y):
""" ⿏标移动时调⽤ """
xSize, ySize = glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT) y = ySize - screen_y
if self.pressed is not None:
dx = x - use_loc[ 0]
dy = y - use_loc[ 1]
if self.pressed == GLUT_RIGHT_BUTTON ackball is not None:
# 变化场景的⾓度
elif self.pressed == GLUT_LEFT_BUTTON:
elif self.pressed == GLUT_MIDDLE_BUTTON:
else:
pass
glutPostRedisplay()
def handle_keystroke(self, key, x, screen_y):
""" 键盘输⼊时调⽤ """
xSize, ySize = glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT) y = ySize - screen_y
if key == 's':
elif key == 'c':
elif key == GLUT_KEY_UP:
elif key == GLUT_KEY_DOWN:
elif key == GLUT_KEY_LEFT:
elif key == GLUT_KEY_RIGHT:
glutPostRedisplay()
内部回调
针对⽤户⾏为会调⽤igger⽅法,它的第⼀个参数指明⾏为期望的效果,后续参数为该效果的参数,trigger的实现如下:
def trigger(self, name, *args, **kwargs):
for func in self.callbacks[name]:
func(*args, **kwargs)
从代码可以看出trigger会取得callbacks词典下该效果对应的所有⽅法逐⼀调⽤。
那么如何将⽅法添加进callbacks呢?我们需要实现⼀个注册回调函数的⽅法:
def register_callback(self, name, func):
self.callbacks[name].append(func)
还记得Viewer中未实现的self.init_interaction()吗,我们就是在这⾥注册回调函数的,下⾯补完init_interaction.
from interaction import Interaction
...
class Viewer(object):
...
def init_interaction(self):
self.interaction = Interaction()
ister_callback( 'pick', self.pick)
ister_callback( 'move', ve)
ister_callback( 'place', self.place)
ister_callback( 'rotate_color', ate_color)
ister_callback( 'scale', self.scale)
def pick(self, x, y):
""" ⿏标选中⼀个节点 """
pass
def move(self, x, y):
""" 移动当前选中的节点 """
pass
def place(self, shape, x, y):
""" 在⿏标的位置上新放置⼀个节点 """
pass
def rotate_color(self, forward):
""" 更改选中节点的颜⾊ """
四川的眼泪pass
def scale(self, up):
""" 改变选中节点的⼤⼩ """
pass
pick、move 等函数的说明如下表所⽰
回调函数参数说明
pick x:number, y:number⿏标选中⼀个节点
move x:number, y:number移动当前选中的节点
place shape:string, x:number, y:number在⿏标的位置上新放置⼀个节点
rotate_color forward:boolean更改选中节点的颜⾊
scale up:boolean改变选中节点的⼤⼩
我们将在之后实现这些函数。
Interaction类抽象出了应⽤层级别的⽤户输⼊接⼝,这意味着当我们希望将glut更换为别的⼯具库的时候,只要照着抽象出来的接⼝重新实现⼀遍底层⼯具的调⽤就⾏了,也就是说仅需改动Interaction类内的代码,实现了模块与模块之间的低耦合。
这个简单的回调系统已满⾜了我们的项⽬所需。在真实的⽣产环境中,⽤户接⼝对象常常是动态⽣成和销毁的,所以真实⽣产中还需要实现解除注册的⽅法,我们这⾥就不⽤啦。
与场景交互
旋转场景
在这个项⽬中摄像机是固定的,我们主要靠移动场景来观察不同⾓度下的3d模型。摄像机固定在距离
原点15个单位的位置,⾯对世界坐标系的原点。感观上是这样,但其实这种说法不准确,真实情况是在世界坐标系⾥摄像机是在原点的,但在摄像机坐标系中,摄像机后退了15个单位,这就等价于前者说的那种情况了。
使⽤轨迹球
我们使⽤轨迹球算法来完成场景的旋转,旋转的⽅法理解起来很简单,想象⼀个可以向任意⾓度围绕球⼼旋转的地球仪,你的视线是不变的,但是通过你的⼿在拨这个球,你可以想看哪⾥拨哪⾥。在我们的项⽬中,这个拨球的⼿就是⿏标右键,你点着右键拖动就能实现这个旋转场景的效果了。
想要更多的理解轨迹球可以参考,在这个项⽬中,我们使⽤中轨迹球的实现。
下载trackball.py⽂件,并将其置于⼯作⽬录下:
$ wget  labfile.oss.aliyuncs/courses/561/trackball.py
drag_to⽅法实现与轨迹球的交互,它会⽐对之前的⿏标位置和移动后的⿏标位置来更新旋转矩阵。
得到的旋转矩阵保存在viewer的trackball.matrix中。
更新viewer.py下的ModelView矩阵
class Viewer(object):
...
def render(self):
self.init_view()
glEnable(GL_LIGHTING)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
松下g5# 将ModelView矩阵设为轨迹球的旋转矩阵
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glLoadIdentity()
glMultMatrixf(ackball.matrix)
# 存储ModelView矩阵与其逆矩阵之后做坐标系转换⽤
currentModelView = numpy.array(glGetFloatv(GL_MODELVIEW_MATRIX))
self.inverseModelView = anspose(currentModelView))
der()
glDisable(GL_LIGHTING)
glCallList(G_OBJ_PLANE)
glPopMatrix()
glFlush()
运⾏代码:
右键拖动查看效果:
选择场景中的对象
既然要操作场景中的对象,那么必然得先能够选中对象,要怎么才能选中呢?想象你有⼀只指哪打哪的激光笔,当激光与对象相交时就相当于选中了对象。芳香烃
我们如何判定激光穿透了对象呢?
想要真正实现对复杂形状物体进⾏选择判定是⾮常考验算法和性能的,所以在这⾥我们简化问题,对对象使⽤包围盒(axis-aligned bounding box, 简称AABB),包围盒可以想象成⼀个为对象量⾝定做的盒⼦,你刚刚好能将模型放进去。这样做的好处就是对于不同形状的对象你都可以使⽤同⼀段代码处理选中判定,并能保证较好的性能。
新建aabb.py,编写包围盒类:
from OpenGL.GL import glCallList, glMatrixMode, glPolygonMode, glPopMatrix, glPushMatrix, glTranslated, \
GL_FILL, GL_FRONT_AND_BACK, GL_LINE, GL_MODELVIEW
from primitive import G_OBJ_CUBE
import numpy
import math
#判断误差
EPSILON = 0.000001
class AABB(object):
def__init__(self, center, size):

本文发布于:2024-09-24 00:29:00,感谢您对本站的认可!

本文链接:https://www.17tex.com/xueshu/212541.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:实现   对象   场景   选中   回调   旋转   函数
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议