Hi all,

I've adapted a trackball code by Roger Allen that was designed for
pyglet. It allows to easily rotate a 3D scene with minimal intrusion.
I also added the possibility to set rotation angles "by hand". Just
run trackball.py to get a working example.

Nicolas

P.S.: Sorry if I'm not supposed to post code here but I'm not used to
google groups and I did not find the way to post a file to the "Files"
page)

-----

#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c)  2009 Nicolas Rougier
#                2008 Roger Allen
#                1993, 1994, Silicon Graphics, Inc.
# ALL RIGHTS RESERVED
# Permission to use, copy, modify, and distribute this software for
# any purpose and without fee is hereby granted, provided that the
above
# copyright notice appear in all copies and that both the copyright
notice
# and this permission notice appear in supporting documentation, and
that
# the name of Silicon Graphics, Inc. not be used in advertising
# or publicity pertaining to distribution of the software without
specific,
# written prior permission.
#
# THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
# AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
# INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
# FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL SILICON
# GRAPHICS, INC.  BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
# SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
# KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
# LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
# THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC.  HAS BEEN
# ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
# POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
#
# US Government Users Restricted Rights
# Use, duplication, or disclosure by the Government is subject to
# restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
# (c)(1)(ii) of the Rights in Technical Data and Computer Software
# clause at DFARS 252.227-7013 and/or in similar or successor
# clauses in the FAR or the DOD or NASA FAR Supplement.
# Unpublished-- rights reserved under the copyright laws of the
# United States.  Contractor/manufacturer is Silicon Graphics,
# Inc., 2011 N.  Shoreline Blvd., Mountain View, CA 94039-7311.
#
# Originally implemented by Gavin Bell, lots of ideas from Thant
Tessman
# and the August '88 issue of Siggraph's "Computer Graphics," pp.
121-129.
# and David M. Ciemiewicz, Mark Grossman, Henry Moreton, and Paul
Haeberli
#
# Note: See the following for more information on quaternions:
#
# - Shoemake, K., Animating rotation with quaternion curves, Computer
#   Graphics 19, No 3 (Proc. SIGGRAPH'85), 245-254, 1985.
# - Pletinckx, D., Quaternion calculus as a basic tool in computer
#   graphics, The Visual Computer 5, 2-13, 1989.
#
-----------------------------------------------------------------------------
''' Provides a virtual trackball for 3D scene viewing

Example usage:

   trackball = Trackball(45,45)

   @window.event
   def on_mouse_drag(x, y, dx, dy, button, modifiers):
       x  = (x*2.0 - window.width)/float(window.width)
       dx = 2*dx/float(window.width)
       y  = (y*2.0 - window.height)/float(window.height)
       dy = 2*dy/float(window.height)
       trackball.drag(x,y,dx,dy)

   @window.event
   def on_resize(width,height):
       glViewport(0, 0, window.width, window.height)
       glMatrixMode(GL_PROJECTION)
       glLoadIdentity()
       gluPerspective(45, window.width / float(window.height), .1,
1000)
       glMatrixMode (GL_MODELVIEW)
       glLoadIdentity ()
       glTranslatef (0, 0, -3)
       glMultMatrixf(trackball.matrix)

You can also set trackball orientation directly by setting theta and
phi value
expressed in degrees. Theta relates to the rotation angle around X
axis while
phi relates to the rotation angle around Z axis.


:requires: pyglet 1.1
'''
__docformat__ = 'restructuredtext'
__version__ = '1.0'

import math
from pyglet.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):
        """ Build a new trackball with specified view """

        self._rotation = [0,0,0,1]
        self._count = 0
        self._matrix=None
        self._RENORMCOUNT = 97
        self._TRACKBALLSIZE = 0.8
        self.__set_orientation(theta,phi)

    #
-------------------------------------------------------------------------
    def drag (self, x, y, dx, dy):
        """ Move trackball view from x,y to x+dx,y+dy.
            Values must be normalized to the range [-1,1].
        """
        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 __get_matrix(self):
        return self._matrix
    matrix = property(__get_matrix,
                     doc='''Model view matrix transformation (read-
only)

                     Model view matrix to be multiplied to the curent
modelview

                    :type: GL matrix
                     ''')

    #
-------------------------------------------------------------------------
    def __get_theta(self):
        self._theta, self._phi = self.__get_orientation()
        return self._theta
    def __set_theta(self, theta):
        self.__set_orientation(theta, self._phi)
    theta = property(__get_theta, __set_theta,
                     doc='''Angle around z axis

                     Angle (in degrees) around the z axis

                    :type: float
                     ''')

    #
-------------------------------------------------------------------------
    def __get_phi(self):
        self._theta, self._phi = self.__get_orientation()
        return self._phi
    def __set_phi(self, phi):
        self.__set_orientation(self._theta, phi)
    phi = property(__get_phi, __set_phi,
                     doc='''Angle around x axis

                     Angle (in degrees) around the x axis

                    :type: float
                     ''')


    #
-------------------------------------------------------------------------
    def __get_orientation(self):
        """ Return current computed orientation. """

        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 ax,-az

    #
-------------------------------------------------------------------------
    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)



#
------------------------------------------------------------------------------
if __name__ == '__main__':
    import pyglet
    from pyglet.gl import *

    window    = pyglet.window.Window(resizable=True)
    trackball = Trackball(theta=45, phi=30)
    font      = pyglet.font.load ('Monaco', 16)
    #font      = pyglet.font.load ('Bitstream Vera Mono', 16)
    text      = pyglet.font.Text (font, valign='top')
    text.text = u'θ:%.1f°, φ:%.1f°' % (trackball.theta, trackball.phi)

    #
--------------------------------------------------------------------------
    class Scene3D(pyglet.graphics.Group):
        def set_state(self):
            """ Push 3d context """
            glMatrixMode(GL_PROJECTION)
            glPushMatrix()
            glLoadIdentity()
            gluPerspective(45, window.width / float(window.height), .
1, 1000)
            glMatrixMode(GL_MODELVIEW)
            glPushMatrix()
            glLoadIdentity()
            glTranslatef(0, 0, -3)
            glMultMatrixf(trackball.matrix)
            glEnable(GL_DEPTH_TEST)

        def unset_state(self):
            """ Pop 3d context """
            glMatrixMode(GL_PROJECTION)
            glPopMatrix()
            glMatrixMode(GL_MODELVIEW)
            glPopMatrix()

    scene = Scene3D()
    batch = pyglet.graphics.Batch()
    batch.add(24, GL_QUADS, scene,
        ('n3f', ( 0, 0, 1)*4+( 0, 0,-1)*4+( 0, 1, 0)*4+
                ( 0,-1, 0)*4+( 1, 0, 0)*4+(-1, 0, 0)*4),
        ('v3f', (-.5,-.5, .5)+( .5,-.5, .5)+( .5, .5, .5)+(-.5, .5, .
5)+
                (-.5,-.5,-.5)+(-.5, .5,-.5)+( .5, .5,-.5)+( .5,-.5,-.
5)+
                (-.5, .5,-.5)+(-.5, .5, .5)+( .5, .5, .5)+( .5, .5,-.
5)+
                (-.5,-.5,-.5)+( .5,-.5,-.5)+( .5,-.5, .5)+(-.5,-.5, .
5)+
                ( .5,-.5,-.5)+( .5, .5,-.5)+( .5, .5, .5)+( .5,-.5, .
5)+
                (-.5,-.5,-.5)+(-.5,-.5, .5)+(-.5, .5, .5)+(-.5, .5,-.
5)),
        ('t2f', (0,0)+(1,0)+(1,1)+(0,1)+
                (1,0)+(1,1)+(0,1)+(0,0)+
                (0,1)+(0,0)+(1,0)+(1,1)+
                (1,1)+(0,1)+(0,0)+(1,0)+
                (1,0)+(1,1)+(0,1)+(0,0)+
                (0,0)+(1,0)+(1,1)+(0,1)))

    #
--------------------------------------------------------------------------
    @window.event
    def on_draw():
        window.clear()
        glPolygonMode (GL_FRONT_AND_BACK, GL_FILL)
        glEnable (GL_POLYGON_OFFSET_FILL)
        glPolygonOffset (1.0, 1.0)
        glColor3f(0.5, 0.5, 0.5)
        batch.draw()
        glDisable (GL_POLYGON_OFFSET_FILL)
        glColor3f(1.0, 1.0, 1.0)
        glPolygonMode (GL_FRONT_AND_BACK, GL_LINE)
        glEnable(GL_BLEND)
        glEnable(GL_LINE_SMOOTH)
        batch.draw()
        glPolygonMode (GL_FRONT_AND_BACK, GL_FILL)
        glDisable(GL_BLEND)
        text.draw()
        fps_display.draw()

    #
--------------------------------------------------------------------------
    @window.event
    def on_resize(width, height):
        text.x, text.y = 5, height

    #
--------------------------------------------------------------------------
    @window.event
    def on_mouse_drag(x, y, dx, dy, button, modifiers):
        x  = (x*2.0 - window.width)/float(window.width)
        dx = 2*dx/float(window.width)
        y  = (y*2.0 - window.height)/float(window.height)
        dy = 2*dy/float(window.height)
        trackball.drag(x,y,dx,dy)
        text.text = u'θ:%.1f°, φ:%.1f°' % (trackball.theta,
trackball.phi)

    fps_display = pyglet.clock.ClockDisplay()
    pyglet.app.run()


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"pyglet-users" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/pyglet-users?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to