#!/usr/bin/env python import sys import time import random import pprint import Xlib from Xlib.display import Display from gi.repository import Gtk, Gdk, GdkX11, GLib, GObject from ctypes import * from numpy import array from OpenGL.GL import * from OpenGL.GL import shaders from OpenGL.GLU import gluPerspective, gluLookAt from OpenGL.arrays import vbo from OpenGL import GLX try: from OpenGL.GLX import struct__XDisplay except ImportError as err: from OpenGL.raw._GLX import struct__XDisplay from OpenGL.GL import GL_VERTEX_SHADER, GL_FRAGMENT_SHADER from OpenGL.GL import shaders, glGetUniformLocation from helper import shader from helper import cube as createcube class gtkgl: """ these method do not seem to exist in python x11 library lets exploit the c methods useful link http://www.opengl.org/wiki/Programming_OpenGL_in_Linux:_GLX_and_Xlib""" xlib = cdll.LoadLibrary('libX11.so') xlib.XOpenDisplay.argtypes = [c_char_p] xlib.XOpenDisplay.restype = POINTER(struct__XDisplay) xdisplay = xlib.XOpenDisplay(None) display = Xlib.display.Display() attrs = [] xwindow_id = None width = height = 500 def __init__(self): """ lets setup are opengl settings and create the context for our window """ self.add_attribute(GLX.GLX_RGBA, True) self.add_attribute(GLX.GLX_RED_SIZE, 8) self.add_attribute(GLX.GLX_GREEN_SIZE, 8) self.add_attribute(GLX.GLX_BLUE_SIZE, 8) self.add_attribute(GLX.GLX_DOUBLEBUFFER, 1) self.add_attribute(GLX.GLX_DEPTH_SIZE, 24) xvinfo = GLX.glXChooseVisual(self.xdisplay, self.display.get_default_screen(), self.get_attributes()) print("run glxinfo to match this visual id %s " % hex(xvinfo.contents.visualid)) self.context = GLX.glXCreateContext(self.xdisplay, xvinfo, None, True) def add_attribute(self, setting, value): """just to nicely add opengl parameters""" self.attrs.append(setting) self.attrs.append(value) def get_attributes(self): """ return our parameters in the expected structure""" attrs = self.attrs + [0, 0] return (c_int * len(attrs))(*attrs) def configure(self, wid): """ """ self.xwindow_id = GdkX11.X11Window.get_xid(wid) if(not GLX.glXMakeCurrent(self.xdisplay, self.xwindow_id, self.context)): print ('failed configuring context') glViewport(0, 0, self.width, self.height) def draw_start(self): """make cairo context current for drawing""" if(not GLX.glXMakeCurrent(self.xdisplay, self.xwindow_id, self.context)): print ("failed to get the context for drawing") def draw_finish(self): """swap buffer when we have finished drawing""" GLX.glXSwapBuffers(self.xdisplay, self.xwindow_id) class scene: width, height = 600, 600 rotationx = 0.0 rotationy = 0.0 rotation_incx = 0.5 rotation_incy = 0.5 radius = 0 touch_count = 0 touch_previous = 0, 0 touch_start_one = 0, 0 touch_start_two = 0, 0 touch_end_one = 0, 0 touch_end_two = 0, 0 touch_time = 0 camera_distance = 25 cube_length = 1.0 cube_size = cube_length / 2 def __init__(self): """setup everything in the correct order""" self.glwrap = gtkgl() self.setup_opengl() self.generate() self.gui() self.mode = 'vbo all cubes' def gui(self): """load in the gui and connect the events and set our properties""" self.start_time = time.time() self.frame = 1 xml = Gtk.Builder() xml.add_from_file('gui.glade') self.window = xml.get_object('window1') self.mode_widget = xml.get_object('cmbmode') self.mode_widget.connect('changed', self.change_mode) self.rotate_widget = xml.get_object('spinrotate') self.rotate_widget.connect('value-changed', self.change_rotate_speed) self.radius_widget = xml.get_object('spinradius') self.radius_widget.connect('value-changed', self.change_radius) self.color_widget = xml.get_object('btncolor') self.color_widget.connect('clicked', self.change_color) self.canvas_widget = xml.get_object('canvas') self.canvas_widget.connect('configure_event', self.on_configure_event) self.canvas_widget.connect('draw', self.on_draw) self.canvas_widget.set_double_buffered(False) self.canvas_widget.set_size_request(self.glwrap.width, self.glwrap.height) self.canvas_widget.add_events(Gdk.EventMask.TOUCH_MASK) self.canvas_widget.connect('touch-event', self.touched) self.window.show_all() GObject.idle_add(self.loop_draw) def touched(self, widget, ev): """basic touch support, count the touches so we no how many fingers basic pinc zoom along the x, single finger slide to rotate""" if ev.get_source_device().get_source() == Gdk.InputSource.TOUCHSCREEN: if ev.touch.type == Gdk.EventType.TOUCH_BEGIN: self.touch_start = ev.touch.x, ev.touch.y self.touch_count += 1 if self.touch_count == 2: self.touch_start_two = ev.touch.x, ev.touch.y self.touch_previous = ev.touch.x, ev.touch.y if ev.touch.type == Gdk.EventType.TOUCH_UPDATE: if ev.touch.time - self.touch_time < 100: return True if self.touch_count == 2: #basic pinch zoom along the x axis d1 = self.touch_previous[0] - ev.touch.x if d1 > 1: self.camera_distance += self.camera_distance * 0.05 self.touch_previous = ev.touch.x, ev.touch.y if d1 < 1: self.camera_distance -= self.camera_distance * 0.05 self.touch_previous = ev.touch.x, ev.touch.y self.update_camera() self.touch_time = ev.touch.time if ev.touch.type == Gdk.EventType.TOUCH_END: self.touch_end = ev.touch.x, ev.touch.y #set rotation when touch ends if self.touch_count == 1: self.rotation_incx = (self.touch_start[0] - self.touch_end[0]) * 0.01 self.rotation_incy = (self.touch_start[1] - self.touch_end[1]) * 0.01 self.touch_count = 0 def in_circle(self, center_x, center_y, center_z, radius, x, y, z): """ test if our cordinate lies inside our sphere""" square_dist = (center_x - x) ** 2 + (center_y - y) ** 2 + (center_z - z) ** 2 return square_dist <= radius ** 2 def change_color(self, widget): #regenerate the scene self.generate() def change_mode(self, widget): #change whats drawn and how self.mode = widget.get_active_text().lower() print(widget.get_active_text()) def change_rotate_speed(self, widget): #handle spinner rotation speed event self.rotation_incx = widget.get_value() self.rotation_incy = widget.get_value() def change_radius(self, widget): #increase size of circle and number of polygons self.radius = int(widget.get_value()) self.generate() def loop_draw(self): #send redraw event to drawing area widget self.canvas_widget.queue_draw() return True def on_configure_event(self, widget, event): """if we recieve a configure event for example a resize, then grab the context settings and resize our scene """ self.glwrap.width = widget.get_allocation().width self.glwrap.height = widget.get_allocation().height self.width, self.height = self.glwrap.width, self.glwrap.height #update our states because we have reconfigured the display self.glwrap.configure(widget.get_window()) self.glwrap.draw_start() self.update_camera() self.setup_shaders() glEnable(GL_DEPTH_TEST) glDepthMask(GL_TRUE) glDepthFunc(GL_LEQUAL) glDepthRange(0.0, 1.0) glEnable(GL_CULL_FACE) glCullFace(GL_BACK) glFrontFace(GL_CW) self.glwrap.draw_finish() return True def on_draw(self, widget, context): """if we recieve a draw event redraw our opengl scene""" self.elapsed_time = time.time() - self.start_time self.frame += 1 if self.elapsed_time > 1: print('fps %d' % self.frame) self.start_time = time.time() self.frame = 1 self.glwrap.draw_start() self.draw() self.glwrap.draw_finish() def generate(self): self.cubes = [] #position cubes inside a sphere radius for shift_x in range(-self.radius, self.radius + 1): for shift_y in range(-self.radius, self.radius + 1): for shift_z in range(-self.radius, self.radius + 1): x = shift_x * self.cube_length y = shift_y * self.cube_length z = shift_z * self.cube_length if not self.in_circle(0, 0, 0, self.radius, x, y, z): continue #random colours / textures if we want color = random.choice([0.85, 0.15]), random.choice([0.85, 0.15]), random.choice([0.85, 0.15]) self.cubes.append(createcube((x, y, z), color, self.cube_size)) self.test_cube = createcube((x, y, z), (random.choice([0.85, 0.15]), random.choice([0.85, 0.15]), random.choice([0.85, 0.15])), 6) faces = [] for cube in self.cubes: faces += cube.get_data() print('Generated %s Cubes' % str(len(self.cubes))) print('Generated %s Tringles' % str(len(faces) / 3)) self.vbuffer = vbo.VBO(array(faces, 'f')) def setup_shaders(self): self.shader_program = shader() self.shader_program.compile() def setup_opengl(self): glShadeModel(GL_SMOOTH) glClearColor(0.0, 0.0, 0.0, 0.0) glClearDepth(1.0) glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST) glPolygonMode(GL_FRONT, GL_FILL) def update_camera(self): glViewport(0, 0, self.width, self.height) glMatrixMode(GL_PROJECTION) glLoadIdentity() gluPerspective(45, 1.0 * self.width / self.height, 1.0, 80.0) gluLookAt(self.camera_distance, self.camera_distance, self.camera_distance, # location 0.0, 0.0, 0.0, # lookat 0.0, 1.0, 0.0) # up direction glMatrixMode(GL_MODELVIEW) glLoadIdentity() def draw_test(self): #lets do a simple rotation so we can see the objects are 3d glRotatef(self.rotationx, 1.0, 0.0, 0.0) self.rotationx += self.rotation_incx glRotatef(self.rotationy, 0.0, 1.0, 0.0) self.rotationy += self.rotation_incy #use our shader program and enable vertex loading glUseProgram(self.shader_program.program) glEnableClientState(GL_VERTEX_ARRAY) glEnableClientState(GL_COLOR_ARRAY) #render the triangles into a virtual buffer object self.test_cube.bind() glVertexPointer(3, GL_FLOAT, 24, self.test_cube.vbuffer) glColorPointer(3, GL_FLOAT, 24, self.test_cube.vbuffer + 12) glDrawArrays(GL_TRIANGLES, 0, self.test_cube.vbuffer_size) self.test_cube.unbind() #restore opengl to our previous state glDisableClientState(GL_COLOR_ARRAY) glDisableClientState(GL_VERTEX_ARRAY) shaders.glUseProgram(0) def draw_vbo_per_cube(self): #lets do a simple rotation so we can see the objects are 3d glRotatef(self.rotationx, 1.0, 0.0, 0.0) self.rotationx += self.rotation_incx glRotatef(self.rotationy, 0.0, 1.0, 0.0) self.rotationy += self.rotation_incy # use our shader program and enable vertex loading glUseProgram(self.shader_program.program) glEnableClientState(GL_VERTEX_ARRAY) glEnableClientState(GL_COLOR_ARRAY) # render the triangles into a virtual buffer object for shape in self.cubes: shape.bind() glVertexPointer(3, GL_FLOAT, 24, shape.vbuffer) glColorPointer(3, GL_FLOAT, 24, shape.vbuffer + 12) glDrawArrays(GL_TRIANGLES, 0, shape.vbuffer_size) shape.unbind() #restore opengl to our previous state glDisableClientState(GL_COLOR_ARRAY) glDisableClientState(GL_VERTEX_ARRAY) shaders.glUseProgram(0) def draw_vbo_all_cubes(self): #lets do a simple rotation so we can see the objects are 3d glRotatef(self.rotationx, 1.0, 0.0, 0.0) self.rotationx += self.rotation_incx glRotatef(self.rotationy, 0.0, 1.0, 0.0) self.rotationy += self.rotation_incy # use our shader program and enable vertex loading glUseProgram(self.shader_program.program) glEnableClientState(GL_VERTEX_ARRAY) glEnableClientState(GL_COLOR_ARRAY) # render the triangles into a virtual buffer object self.vbuffer.bind() glVertexPointer(3, GL_FLOAT, 24, self.vbuffer) glColorPointer(3, GL_FLOAT, 24, self.vbuffer + 12) glDrawArrays(GL_TRIANGLES, 0, len(self.vbuffer)) self.vbuffer.unbind() #restore opengl to our previous state glDisableClientState(GL_COLOR_ARRAY) glDisableClientState(GL_VERTEX_ARRAY) shaders.glUseProgram(0) def draw(self): glEnable(GL_DEPTH_TEST) glClearDepth(1.0) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glLoadIdentity() if self.mode == 'vbo test cube': self.draw_test() if self.mode == 'vbo per cube': self.draw_vbo_per_cube() if self.mode == 'vbo all cubes': self.draw_vbo_all_cubes() if __name__ == '__main__': glexample = scene() GLib.threads_init() Gdk.threads_init() Gdk.threads_enter() Gtk.main() Gdk.threads_leave()