Rotating cube

Simulation of a rotating cube using python + GTk.
Moving the mouse while the button1 is pressed will produce a rotation of the cube along XZ, and YX axis. Otherwise you will shift the point of view on the XY plane. Still looking for a more efficient way of drawing on the screen.

#!/usr/bin/env python

import gtk
import gobject
from math import pi, cos, sin

# perspective
ZOOM = 100.0
FOCUS = 100.0

# misc
FPS=60
DEPTH = 16
PALETTE = {
        'black': (0, 0, 0),
        'blue': (0, 0, 65535),
        'green': (0, 65535, 0),
        'cyan': (0, 65535, 65535),
        'red': (65335, 0, 0),
        'magenta': (65535, 0, 65535),
        'yellow': (65535, 65535, 0),
        'white': (65535, 65535, 65535),
}
ZSHIFT = 3
ZMOD = 200/DEPTH

class Scene():
        def __init__(self):
                window = gtk.Window()
                window.set_size_request(640, 480)
                window.add_events(gtk.gdk.BUTTON_PRESS_MASK
                                | gtk.gdk.POINTER_MOTION_MASK
                                | gtk.gdk.POINTER_MOTION_HINT_MASK)
                window.connect('delete_event', self.delete_event_cb)
                window.connect('button_press_event', self.button_press_event)
                window.connect('motion_notify_event', self.motion_notify_event)
                self.darea = gtk.DrawingArea()
                self.darea.connect('configure_event', self.configure_event)
                self.darea.connect('expose_event', self.expose_event)
                window.add(self.darea)
                self.darea.show()
                window.show()
                self.refresh = 0
                self.refresh = gobject.timeout_add(1000/FPS, self.draw_scene)

        def delete_event_cb(self, widget, event):
                if self.refresh != 0:
                        gobject.source_remove(self.refresh)
                        self.refresh = 0
                gtk.main_quit()
        def button_press_event(self, widget, event):
                if event.button == 1:
                        x, y, state = event.window.get_pointer()
                        self.oldx, self.oldy = self.convert_dev_to_user(x, y)
                return True

        def motion_notify_event(self, widget, event):
                if event.is_hint:
                        x, y, state = event.window.get_pointer()
                else:
                        x = event.x
                        y = event.y
                        state = event.state
                # the origin is in the middle of the windonw
                x, y = self.convert_dev_to_user(x, y)
                if state & gtk.gdk.BUTTON1_MASK:
                        angle_xz = angle_yz = 0
                        if x > self.oldx:
                                angle_xz = .05
                        elif x < self.oldx:
                                angle_xz = -.05
                        if y > self.oldy:
                                angle_yz = .05
                        elif y < self.oldy:
                                angle_yz = -.05
                        self.cube.rotate(angle_xz, angle_yz)
                        self.oldx = x
                        self.oldy = y
                else:
                        self.vx = x
                        self.vy = y
                return True

        def configure_event(self, widget, event):
                x, y, self.width, self.height = widget.get_allocation()
                self.cx = self.width/2
                self.cy = self.height/2
                self.unit = min(self.cx, self.cy)/2
                self.vx = self.vy = 0
                self.oldx = self.oldy = 0
                self.pixmap = gtk.gdk.Pixmap(widget.window, self.width,
                                self.height)
                self.cube = Cube(self)
                self.draw_scene()
                return True

        def expose_event(self, widget, event):
                x , y, width, height = event.area
                self.darea_realize(x, y, width, height)
                return False

        def darea_realize(self, x, y, width, height):
                self.darea.window.draw_drawable(
                                self.darea.get_style().fg_gc[gtk.STATE_NORMAL],
                                self.pixmap, x, y, x, y, width, height)

        def convert_dev_to_user(self, x, y):
                return x - self.cx, (self.height - 1 - y) - self.cy

        def z_scale(self, z):
                return ZOOM/(FOCUS + z)

        def convert_3d_to_2d(self, point):
                px, py, pz = point
                px *= self.unit
                py *= self.unit
                z = (pz + ZSHIFT)*ZMOD
                x = self.cx + self.vx + (px - self.vx)*self.z_scale(z)
                y = self.cy + self.vy + (py - self.vy)*self.z_scale(z)
                return int(x), int(y)

        def color_shading(self, color, z):
                z += ZSHIFT
                r, g, b = PALETTE[color]
                # quadratic shading
                ratio = (float(DEPTH) - z)**2/(DEPTH**2)
                r = int(r*ratio)
                g = int(g*ratio)
                b = int(b*ratio)
                return r, g, b

        def draw_line(self, src, dst, color='white', dashed=False):
                gc = self.darea.window.new_gc()
                r, g, b = self.color_shading(color, src[2])
                gc.foreground = self.darea.get_colormap().alloc_color(r, g, b)
                if dashed:
                       gc.set_line_attributes(1, gtk.gdk.LINE_DOUBLE_DASH,
                                       gtk.gdk.CAP_BUTT, gtk.gdk.JOIN_BEVEL)
                x0, y0 = self.convert_3d_to_2d(src)
                x1, y1 = self.convert_3d_to_2d(dst)
                self.pixmap.draw_line(gc,
                                x0, self.height - 1 - (y0),
                                x1, self.height - 1 - (y1))

        def draw_scene(self):
                self.pixmap.draw_rectangle(self.darea.get_style().black_gc,
                                True, 0, 0, self.width, self.height)
                self.cube.draw()
                self.darea_realize(0, 0, -1, -1)
                return True

class Cube(Scene):
        def __init__(self, scene):
                self.scene = scene
                self.front = [(-1, 1, -1), (1, 1, -1), (1, -1, -1), (-1, -1, -1)]
                self.rear = [(-1, 1, 1), (1, 1, 1), (1, -1, 1), (-1, -1, 1)]

        def rotate(self, angle_XZ, angle_YZ):
                scene = self.scene
                for i in range(4):
                        # Front face
                        x, y, z = self.front[i]
                        theta = angle_XZ
                        x, y, z = (x*cos(theta) - z*sin(theta), y, x*sin(theta) + z*cos(theta))
                        theta = angle_YZ
                        x, y, z = (x, y*cos(theta) - z*sin(theta), y*sin(theta) + z*cos(theta))
                        self.front[i] = (x, y, z)
                        # Rear face
                        x, y, z = self.rear[i]
                        theta = angle_XZ
                        x, y, z = (x*cos(theta) - z*sin(theta), y, x*sin(theta) + z*cos(theta))
                        theta = angle_YZ
                        x, y, z = (x, y*cos(theta) - z*sin(theta), y*sin(theta) + z*cos(theta))
                        self.rear[i] = (x, y, z)

        def draw(self):
                scene = self.scene
                for i in range(4):
                        scene.draw_line(self.front[i], self.front[(i + 1)%4])
                        scene.draw_line(self.rear[i], self.rear[(i + 1)%4])
                        scene.draw_line(self.front[i], self.rear[i])

if __name__ == '__main__':
        Scene()
        gtk.main()

Tags: , ,

Leave a Reply

You must be logged in to post a comment.