static-sites/bases/do-blog/resources/documents/python/gtk3/tut15/tut15-opengl-touch-app-01.py

383 lines
14 KiB
Python
Executable File

#!/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()