Python>>>创建一个简单的3D场景(2)

使用trackball 来旋转场景

工程文件下载

效果如下:

当鼠标点击和在窗口移动时,x,y的值以左上角为原点,

首先变换成左下角为原点。然后整个窗口map到【-1,1】,【-1,1】

的平面上。

x  = (x*2.0 - width)/width
dx = (2.*dx)/width
y = (y*2.0 - height)/height
dy = (2.*dy)/height

trackball 原理,在viewport上“增加”一个半球体,x,y值都和平面上的一样。

从p1移动到p2

计算出移动轴

 

和角度。

 


trackball代码

__docformat__ = 'restructuredtext'
__version__ = '1.0'

import math
import OpenGL.GL as gl
from OpenGL.GL import GLfloat


# Some useful functions on vectors
# -----------------------------------------------------------------------------
def _v_add(v1, v2):
    return [v1[0]+v2[0], v1[1]+v2[1], v1[2]+v2[2]]
def _v_sub(v1, v2):
    return [v1[0]-v2[0], v1[1]-v2[1], v1[2]-v2[2]]
def _v_mul(v, s):
    return [v[0]*s, v[1]*s, v[2]*s]
def _v_dot(v1, v2):
    return v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2]
def _v_cross(v1, v2):
    return [(v1[1]*v2[2]) - (v1[2]*v2[1]),
            (v1[2]*v2[0]) - (v1[0]*v2[2]),
            (v1[0]*v2[1]) - (v1[1]*v2[0])]
def _v_length(v):
    return math.sqrt(_v_dot(v,v))
def _v_normalize(v):
    try:                      return _v_mul(v,1.0/_v_length(v))
    except ZeroDivisionError: return v

# Some useful functions on quaternions
# -----------------------------------------------------------------------------
def _q_add(q1,q2):
    t1 = _v_mul(q1, q2[3])
    t2 = _v_mul(q2, q1[3])
    t3 = _v_cross(q2, q1)
    tf = _v_add(t1, t2)
    tf = _v_add(t3, tf)
    tf.append(q1[3]*q2[3]-_v_dot(q1,q2))
    return tf
def _q_mul(q, s):
    return [q[0]*s, q[1]*s, q[2]*s, q[3]*s]
def _q_dot(q1, q2):
    return q1[0]*q2[0] + q1[1]*q2[1] + q1[2]*q2[2] + q1[3]*q2[3]
def _q_length(q):
    return math.sqrt(_q_dot(q,q))
def _q_normalize(q):
    try:                      return _q_mul(q,1.0/_q_length(q))
    except ZeroDivisionError: return q
def _q_from_axis_angle(v, phi):
    q = _v_mul(_v_normalize(v), math.sin(phi/2.0))
    q.append(math.cos(phi/2.0))
    return q
def _q_rotmatrix(q):
    m = [0.0]*16
    m[0*4+0] = 1.0 - 2.0*(q[1]*q[1] + q[2]*q[2])
    m[0*4+1] = 2.0 * (q[0]*q[1] - q[2]*q[3])
    m[0*4+2] = 2.0 * (q[2]*q[0] + q[1]*q[3])
    m[0*4+3] = 0.0
    m[1*4+0] = 2.0 * (q[0]*q[1] + q[2]*q[3])
    m[1*4+1] = 1.0 - 2.0*(q[2]*q[2] + q[0]*q[0])
    m[1*4+2] = 2.0 * (q[1]*q[2] - q[0]*q[3])
    m[1*4+3] = 0.0
    m[2*4+0] = 2.0 * (q[2]*q[0] - q[1]*q[3])
    m[2*4+1] = 2.0 * (q[1]*q[2] + q[0]*q[3])
    m[2*4+2] = 1.0 - 2.0*(q[1]*q[1] + q[0]*q[0])
    m[3*4+3] = 1.0
    return m



class Trackball(object):
    ''' Virtual trackball for 3D scene viewing. '''

    def __init__(self, theta=0, phi=0, zoom=1, distance=3):
        ''' Build a new trackball with specified view '''

        self._rotation = [0,0,0,1]
        self.zoom = zoom
        self.distance = distance
        self._count = 0
        self._matrix=None
        self._RENORMCOUNT = 97
        self._TRACKBALLSIZE = 0.8
        self._set_orientation(theta,phi)
        self._x = 0.0
        self._y = 0.0

    def drag_to (self, x, y, dx, dy):
        ''' Move trackball view from x,y to x+dx,y+dy. '''
        viewport = gl.glGetIntegerv(gl.GL_VIEWPORT)
        width,height = float(viewport[2]), float(viewport[3])
        x  = (x*2.0 - width)/width
        dx = (2.*dx)/width
        y  = (y*2.0 - height)/height
        dy = (2.*dy)/height
        q = self._rotate(x,y,dx,dy)
        self._rotation = _q_add(q,self._rotation)
        self._count += 1
        if self._count > self._RENORMCOUNT:
            self._rotation = _q_normalize(self._rotation)
            self._count = 0
        m = _q_rotmatrix(self._rotation)
        self._matrix = (GLfloat*len(m))(*m)

    def zoom_to (self, x, y, dx, dy):
        ''' Zoom trackball by a factor dy '''
        viewport = gl.glGetIntegerv(gl.GL_VIEWPORT)
        height = float(viewport[3])
        self.zoom = self.zoom-5*dy/height

    def pan_to (self, x, y, dx, dy):
        ''' Pan trackball by a factor dx,dy '''
        self.x += dx*0.1
        self.y += dy*0.1


    def push(self):
        viewport = gl.glGetIntegerv(gl.GL_VIEWPORT)
        gl.glMatrixMode(gl.GL_PROJECTION)
        gl.glPushMatrix()
        gl.glLoadIdentity ()
        aspect = viewport[2]/float(viewport[3])
        aperture = 35.0
        near = 0.1
        far = 100.0
        top = math.tan(aperture*3.14159/360.0) * near * self._zoom
        bottom = -top
        left = aspect * bottom
        right = aspect * top
        gl.glFrustum (left, right, bottom, top, near, far)
        gl.glMatrixMode (gl.GL_MODELVIEW)
        gl.glPushMatrix()
        gl.glLoadIdentity ()
        # gl.glTranslate (0.0, 0, -self._distance)
        gl.glTranslate (self._x, self._y, -self._distance)
        gl.glMultMatrixf (self._matrix)

    def pop(void):
        gl.glMatrixMode(gl.GL_MODELVIEW)
        gl.glPopMatrix()
        gl.glMatrixMode(gl.GL_PROJECTION)
        gl.glPopMatrix()

    def _get_matrix(self):
        return self._matrix
    matrix = property(_get_matrix,
                     doc='''Model view matrix transformation (read-only)''')

    def _get_zoom(self):
        return self._zoom
    def _set_zoom(self, zoom):
        self._zoom = zoom
        if self._zoom < .25: self._zoom = .25
        if self._zoom > 10: self._zoom = 10
    zoom = property(_get_zoom, _set_zoom,
                     doc='''Zoom factor''')

    def _get_distance(self):
        return self._distance
    def _set_distance(self, distance):
        self._distance = distance
        if self._distance < 1: self._distance= 1
    distance = property(_get_distance, _set_distance,
                        doc='''Scene distance from point of view''')

    def _get_theta(self):
        self._theta, self._phi = self._get_orientation()
        return self._theta
    def _set_theta(self, theta):
        self._set_orientation(math.fmod(theta,360.0),
                              math.fmod(self._phi,360.0))
    theta = property(_get_theta, _set_theta,
                     doc='''Angle (in degrees) around the z axis''')


    def _get_phi(self):
        self._theta, self._phi = self._get_orientation()
        return self._phi
    def _set_phi(self, phi):
        self._set_orientation(math.fmod(self._theta,360.),
                              math.fmod(phi,360.0))
    phi = property(_get_phi, _set_phi,
                     doc='''Angle around x axis''')


    def _get_orientation(self):
        ''' Return current computed orientation (theta,phi). ''' 

        q0,q1,q2,q3 = self._rotation
        ax = math.atan(2*(q0*q1+q2*q3)/(1-2*(q1*q1+q2*q2)))*180.0/math.pi
        az = math.atan(2*(q0*q3+q1*q2)/(1-2*(q2*q2+q3*q3)))*180.0/math.pi
        return -az,ax

    def _set_orientation(self, theta, phi):
        ''' Computes rotation corresponding to theta and phi. ''' 

        self._theta = theta
        self._phi = phi
        angle = self._theta*(math.pi/180.0)
        sine = math.sin(0.5*angle)
        xrot = [1*sine, 0, 0, math.cos(0.5*angle)]
        angle = self._phi*(math.pi/180.0)
        sine = math.sin(0.5*angle);
        zrot = [0, 0, sine, math.cos(0.5*angle)]
        self._rotation = _q_add(xrot, zrot)
        m = _q_rotmatrix(self._rotation)
        self._matrix = (GLfloat*len(m))(*m)


    def _project(self, r, x, y):
        ''' Project an x,y pair onto a sphere of radius r OR a hyperbolic sheet
            if we are away from the center of the sphere.
        '''

        d = math.sqrt(x*x + y*y)
        if (d < r * 0.70710678118654752440):    # Inside sphere
            z = math.sqrt(r*r - d*d)
        else:                                   # On hyperbola
            t = r / 1.41421356237309504880
            z = t*t / d
        return z


    def _rotate(self, x, y, dx, dy): 
        ''' Simulate a track-ball.

            Project the points onto the virtual trackball, then figure out the
            axis of rotation, which is the cross product of x,y and x+dx,y+dy.

            Note: This is a deformed trackball-- this is a trackball in the
            center, but is deformed into a hyperbolic sheet of rotation away
            from the center.  This particular function was chosen after trying
            out several variations.
        '''

        if not dx and not dy:
            return [ 0.0, 0.0, 0.0, 1.0]
        last = [x, y,       self._project(self._TRACKBALLSIZE, x, y)]
        new  = [x+dx, y+dy, self._project(self._TRACKBALLSIZE, x+dx, y+dy)]
        a = _v_cross(new, last)
        d = _v_sub(last, new)
        t = _v_length(d) / (2.0*self._TRACKBALLSIZE)
        if (t > 1.0): t = 1.0
        if (t < -1.0): t = -1.0
        phi = 2.0 * math.asin(t)
        return _q_from_axis_angle(a,phi)


    def __str__(self):
        phi = str(self.phi)
        theta = str(self.theta)
        zoom = str(self.zoom)
        return 'Trackball(phi=%s,theta=%s,zoom=%s)' % (phi,theta,zoom)

    def __repr__(self):
        phi = str(self.phi)
        theta = str(self.theta)
        zoom = str(self.zoom)
        return 'Trackball(phi=%s,theta=%s,zoom=%s)' % (phi,theta,zoom)

 

posted on 2017-01-11 14:33  legion  阅读(621)  评论(0编辑  收藏  举报

导航