Traktor/myenv/Lib/site-packages/pygame/examples/glcube.py
2024-05-26 05:12:46 +02:00

592 lines
16 KiB
Python

#!/usr/bin/env python
""" pygame.examples.glcube
Draw a cube on the screen.
Amazing.
Every frame we orbit the camera around a small amount
creating the illusion of a spinning object.
First we setup some points of a multicolored cube. Then we then go through
a semi-unoptimized loop to draw the cube points onto the screen.
OpenGL does all the hard work for us. :]
Keyboard Controls
-----------------
* ESCAPE key to quit
* f key to toggle fullscreen.
"""
import math
import ctypes
import pygame as pg
try:
import OpenGL.GL as GL
import OpenGL.GLU as GLU
except ImportError:
print("pyopengl missing. The GLCUBE example requires: pyopengl numpy")
raise SystemExit
try:
from numpy import array, dot, eye, zeros, float32, uint32
except ImportError:
print("numpy missing. The GLCUBE example requires: pyopengl numpy")
raise SystemExit
# do we want to use the 'modern' OpenGL API or the old one?
# This example shows you how to do both.
USE_MODERN_GL = True
# Some simple data for a colored cube here we have the 3D point position
# and color for each corner. A list of indices describes each face, and a
# list of indices describes each edge.
CUBE_POINTS = (
(0.5, -0.5, -0.5),
(0.5, 0.5, -0.5),
(-0.5, 0.5, -0.5),
(-0.5, -0.5, -0.5),
(0.5, -0.5, 0.5),
(0.5, 0.5, 0.5),
(-0.5, -0.5, 0.5),
(-0.5, 0.5, 0.5),
)
# colors are 0-1 floating values
CUBE_COLORS = (
(1, 0, 0),
(1, 1, 0),
(0, 1, 0),
(0, 0, 0),
(1, 0, 1),
(1, 1, 1),
(0, 0, 1),
(0, 1, 1),
)
CUBE_QUAD_VERTS = (
(0, 1, 2, 3),
(3, 2, 7, 6),
(6, 7, 5, 4),
(4, 5, 1, 0),
(1, 5, 7, 2),
(4, 0, 3, 6),
)
CUBE_EDGES = (
(0, 1),
(0, 3),
(0, 4),
(2, 1),
(2, 3),
(2, 7),
(6, 3),
(6, 4),
(6, 7),
(5, 1),
(5, 4),
(5, 7),
)
def translate(matrix, x=0.0, y=0.0, z=0.0):
"""
Translate (move) a matrix in the x, y and z axes.
:param matrix: Matrix to translate.
:param x: direction and magnitude to translate in x axis. Defaults to 0.
:param y: direction and magnitude to translate in y axis. Defaults to 0.
:param z: direction and magnitude to translate in z axis. Defaults to 0.
:return: The translated matrix.
"""
translation_matrix = array(
[
[1.0, 0.0, 0.0, x],
[0.0, 1.0, 0.0, y],
[0.0, 0.0, 1.0, z],
[0.0, 0.0, 0.0, 1.0],
],
dtype=matrix.dtype,
).T
matrix[...] = dot(matrix, translation_matrix)
return matrix
def frustum(left, right, bottom, top, znear, zfar):
"""
Build a perspective matrix from the clipping planes, or camera 'frustrum'
volume.
:param left: left position of the near clipping plane.
:param right: right position of the near clipping plane.
:param bottom: bottom position of the near clipping plane.
:param top: top position of the near clipping plane.
:param znear: z depth of the near clipping plane.
:param zfar: z depth of the far clipping plane.
:return: A perspective matrix.
"""
perspective_matrix = zeros((4, 4), dtype=float32)
perspective_matrix[0, 0] = +2.0 * znear / (right - left)
perspective_matrix[2, 0] = (right + left) / (right - left)
perspective_matrix[1, 1] = +2.0 * znear / (top - bottom)
perspective_matrix[3, 1] = (top + bottom) / (top - bottom)
perspective_matrix[2, 2] = -(zfar + znear) / (zfar - znear)
perspective_matrix[3, 2] = -2.0 * znear * zfar / (zfar - znear)
perspective_matrix[2, 3] = -1.0
return perspective_matrix
def perspective(fovy, aspect, znear, zfar):
"""
Build a perspective matrix from field of view, aspect ratio and depth
planes.
:param fovy: the field of view angle in the y axis.
:param aspect: aspect ratio of our view port.
:param znear: z depth of the near clipping plane.
:param zfar: z depth of the far clipping plane.
:return: A perspective matrix.
"""
h = math.tan(fovy / 360.0 * math.pi) * znear
w = h * aspect
return frustum(-w, w, -h, h, znear, zfar)
def rotate(matrix, angle, x, y, z):
"""
Rotate a matrix around an axis.
:param matrix: The matrix to rotate.
:param angle: The angle to rotate by.
:param x: x of axis to rotate around.
:param y: y of axis to rotate around.
:param z: z of axis to rotate around.
:return: The rotated matrix
"""
angle = math.pi * angle / 180
c, s = math.cos(angle), math.sin(angle)
n = math.sqrt(x * x + y * y + z * z)
x, y, z = x / n, y / n, z / n
cx, cy, cz = (1 - c) * x, (1 - c) * y, (1 - c) * z
rotation_matrix = array(
[
[cx * x + c, cy * x - z * s, cz * x + y * s, 0],
[cx * y + z * s, cy * y + c, cz * y - x * s, 0],
[cx * z - y * s, cy * z + x * s, cz * z + c, 0],
[0, 0, 0, 1],
],
dtype=matrix.dtype,
).T
matrix[...] = dot(matrix, rotation_matrix)
return matrix
class Rotation:
"""
Data class that stores rotation angles in three axes.
"""
def __init__(self):
self.theta = 20
self.phi = 40
self.psi = 25
def drawcube_old():
"""
Draw the cube using the old open GL methods pre 3.2 core context.
"""
allpoints = list(zip(CUBE_POINTS, CUBE_COLORS))
GL.glBegin(GL.GL_QUADS)
for face in CUBE_QUAD_VERTS:
for vert in face:
pos, color = allpoints[vert]
GL.glColor3fv(color)
GL.glVertex3fv(pos)
GL.glEnd()
GL.glColor3f(1.0, 1.0, 1.0)
GL.glBegin(GL.GL_LINES)
for line in CUBE_EDGES:
for vert in line:
pos, color = allpoints[vert]
GL.glVertex3fv(pos)
GL.glEnd()
def init_gl_stuff_old():
"""
Initialise open GL, prior to core context 3.2
"""
GL.glEnable(GL.GL_DEPTH_TEST) # use our zbuffer
# setup the camera
GL.glMatrixMode(GL.GL_PROJECTION)
GL.glLoadIdentity()
GLU.gluPerspective(45.0, 640 / 480.0, 0.1, 100.0) # setup lens
GL.glTranslatef(0.0, 0.0, -3.0) # move back
GL.glRotatef(25, 1, 0, 0) # orbit higher
def init_gl_modern(display_size):
"""
Initialise open GL in the 'modern' open GL style for open GL versions
greater than 3.1.
:param display_size: Size of the window/viewport.
"""
# Create shaders
# --------------------------------------
vertex_code = """
#version 150
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform vec4 colour_mul;
uniform vec4 colour_add;
in vec4 vertex_colour; // vertex colour in
in vec3 vertex_position;
out vec4 vertex_color_out; // vertex colour out
void main()
{
vertex_color_out = (colour_mul * vertex_colour) + colour_add;
gl_Position = projection * view * model * vec4(vertex_position, 1.0);
}
"""
fragment_code = """
#version 150
in vec4 vertex_color_out; // vertex colour from vertex shader
out vec4 fragColor;
void main()
{
fragColor = vertex_color_out;
}
"""
program = GL.glCreateProgram()
vertex = GL.glCreateShader(GL.GL_VERTEX_SHADER)
fragment = GL.glCreateShader(GL.GL_FRAGMENT_SHADER)
GL.glShaderSource(vertex, vertex_code)
GL.glCompileShader(vertex)
# this logs issues the shader compiler finds.
log = GL.glGetShaderInfoLog(vertex)
if isinstance(log, bytes):
log = log.decode()
for line in log.split("\n"):
print(line)
GL.glAttachShader(program, vertex)
GL.glShaderSource(fragment, fragment_code)
GL.glCompileShader(fragment)
# this logs issues the shader compiler finds.
log = GL.glGetShaderInfoLog(fragment)
if isinstance(log, bytes):
log = log.decode()
for line in log.split("\n"):
print(line)
GL.glAttachShader(program, fragment)
GL.glValidateProgram(program)
GL.glLinkProgram(program)
GL.glDetachShader(program, vertex)
GL.glDetachShader(program, fragment)
GL.glUseProgram(program)
# Create vertex buffers and shader constants
# ------------------------------------------
# Cube Data
vertices = zeros(
8, [("vertex_position", float32, 3), ("vertex_colour", float32, 4)]
)
vertices["vertex_position"] = [
[1, 1, 1],
[-1, 1, 1],
[-1, -1, 1],
[1, -1, 1],
[1, -1, -1],
[1, 1, -1],
[-1, 1, -1],
[-1, -1, -1],
]
vertices["vertex_colour"] = [
[0, 1, 1, 1],
[0, 0, 1, 1],
[0, 0, 0, 1],
[0, 1, 0, 1],
[1, 1, 0, 1],
[1, 1, 1, 1],
[1, 0, 1, 1],
[1, 0, 0, 1],
]
filled_cube_indices = array(
[
0,
1,
2,
0,
2,
3,
0,
3,
4,
0,
4,
5,
0,
5,
6,
0,
6,
1,
1,
6,
7,
1,
7,
2,
7,
4,
3,
7,
3,
2,
4,
7,
6,
4,
6,
5,
],
dtype=uint32,
)
outline_cube_indices = array(
[0, 1, 1, 2, 2, 3, 3, 0, 4, 7, 7, 6, 6, 5, 5, 4, 0, 5, 1, 6, 2, 7, 3, 4],
dtype=uint32,
)
shader_data = {"buffer": {}, "constants": {}}
GL.glBindVertexArray(GL.glGenVertexArrays(1)) # Have to do this first
shader_data["buffer"]["vertices"] = GL.glGenBuffers(1)
GL.glBindBuffer(GL.GL_ARRAY_BUFFER, shader_data["buffer"]["vertices"])
GL.glBufferData(GL.GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL.GL_DYNAMIC_DRAW)
stride = vertices.strides[0]
offset = ctypes.c_void_p(0)
loc = GL.glGetAttribLocation(program, "vertex_position")
GL.glEnableVertexAttribArray(loc)
GL.glVertexAttribPointer(loc, 3, GL.GL_FLOAT, False, stride, offset)
offset = ctypes.c_void_p(vertices.dtype["vertex_position"].itemsize)
loc = GL.glGetAttribLocation(program, "vertex_colour")
GL.glEnableVertexAttribArray(loc)
GL.glVertexAttribPointer(loc, 4, GL.GL_FLOAT, False, stride, offset)
shader_data["buffer"]["filled"] = GL.glGenBuffers(1)
GL.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, shader_data["buffer"]["filled"])
GL.glBufferData(
GL.GL_ELEMENT_ARRAY_BUFFER,
filled_cube_indices.nbytes,
filled_cube_indices,
GL.GL_STATIC_DRAW,
)
shader_data["buffer"]["outline"] = GL.glGenBuffers(1)
GL.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, shader_data["buffer"]["outline"])
GL.glBufferData(
GL.GL_ELEMENT_ARRAY_BUFFER,
outline_cube_indices.nbytes,
outline_cube_indices,
GL.GL_STATIC_DRAW,
)
shader_data["constants"]["model"] = GL.glGetUniformLocation(program, "model")
GL.glUniformMatrix4fv(shader_data["constants"]["model"], 1, False, eye(4))
shader_data["constants"]["view"] = GL.glGetUniformLocation(program, "view")
view = translate(eye(4), z=-6)
GL.glUniformMatrix4fv(shader_data["constants"]["view"], 1, False, view)
shader_data["constants"]["projection"] = GL.glGetUniformLocation(
program, "projection"
)
GL.glUniformMatrix4fv(shader_data["constants"]["projection"], 1, False, eye(4))
# This colour is multiplied with the base vertex colour in producing
# the final output
shader_data["constants"]["colour_mul"] = GL.glGetUniformLocation(
program, "colour_mul"
)
GL.glUniform4f(shader_data["constants"]["colour_mul"], 1, 1, 1, 1)
# This colour is added on to the base vertex colour in producing
# the final output
shader_data["constants"]["colour_add"] = GL.glGetUniformLocation(
program, "colour_add"
)
GL.glUniform4f(shader_data["constants"]["colour_add"], 0, 0, 0, 0)
# Set GL drawing data
# -------------------
GL.glClearColor(0, 0, 0, 0)
GL.glPolygonOffset(1, 1)
GL.glEnable(GL.GL_LINE_SMOOTH)
GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA)
GL.glDepthFunc(GL.GL_LESS)
GL.glHint(GL.GL_LINE_SMOOTH_HINT, GL.GL_NICEST)
GL.glLineWidth(1.0)
projection = perspective(45.0, display_size[0] / float(display_size[1]), 2.0, 100.0)
GL.glUniformMatrix4fv(shader_data["constants"]["projection"], 1, False, projection)
return shader_data, filled_cube_indices, outline_cube_indices
def draw_cube_modern(shader_data, filled_cube_indices, outline_cube_indices, rotation):
"""
Draw a cube in the 'modern' Open GL style, for post 3.1 versions of
open GL.
:param shader_data: compile vertex & pixel shader data for drawing a cube.
:param filled_cube_indices: the indices to draw the 'filled' cube.
:param outline_cube_indices: the indices to draw the 'outline' cube.
:param rotation: the current rotations to apply.
"""
GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
# Filled cube
GL.glDisable(GL.GL_BLEND)
GL.glEnable(GL.GL_DEPTH_TEST)
GL.glEnable(GL.GL_POLYGON_OFFSET_FILL)
GL.glUniform4f(shader_data["constants"]["colour_mul"], 1, 1, 1, 1)
GL.glUniform4f(shader_data["constants"]["colour_add"], 0, 0, 0, 0.0)
GL.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, shader_data["buffer"]["filled"])
GL.glDrawElements(
GL.GL_TRIANGLES, len(filled_cube_indices), GL.GL_UNSIGNED_INT, None
)
# Outlined cube
GL.glDisable(GL.GL_POLYGON_OFFSET_FILL)
GL.glEnable(GL.GL_BLEND)
GL.glUniform4f(shader_data["constants"]["colour_mul"], 0, 0, 0, 0.0)
GL.glUniform4f(shader_data["constants"]["colour_add"], 1, 1, 1, 1.0)
GL.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, shader_data["buffer"]["outline"])
GL.glDrawElements(GL.GL_LINES, len(outline_cube_indices), GL.GL_UNSIGNED_INT, None)
# Rotate cube
# rotation.theta += 1.0 # degrees
rotation.phi += 1.0 # degrees
# rotation.psi += 1.0 # degrees
model = eye(4, dtype=float32)
# rotate(model, rotation.theta, 0, 0, 1)
rotate(model, rotation.phi, 0, 1, 0)
rotate(model, rotation.psi, 1, 0, 0)
GL.glUniformMatrix4fv(shader_data["constants"]["model"], 1, False, model)
def main():
"""run the demo"""
# initialize pygame and setup an opengl display
pg.init()
gl_version = (3, 0) # GL Version number (Major, Minor)
if USE_MODERN_GL:
gl_version = (3, 2) # GL Version number (Major, Minor)
# By setting these attributes we can choose which Open GL Profile
# to use, profiles greater than 3.2 use a different rendering path
pg.display.gl_set_attribute(pg.GL_CONTEXT_MAJOR_VERSION, gl_version[0])
pg.display.gl_set_attribute(pg.GL_CONTEXT_MINOR_VERSION, gl_version[1])
pg.display.gl_set_attribute(
pg.GL_CONTEXT_PROFILE_MASK, pg.GL_CONTEXT_PROFILE_CORE
)
fullscreen = False # start in windowed mode
display_size = (640, 480)
pg.display.set_mode(display_size, pg.OPENGL | pg.DOUBLEBUF | pg.RESIZABLE)
if USE_MODERN_GL:
gpu, f_indices, o_indices = init_gl_modern(display_size)
rotation = Rotation()
else:
init_gl_stuff_old()
going = True
while going:
# check for quit'n events
events = pg.event.get()
for event in events:
if event.type == pg.QUIT or (
event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE
):
going = False
elif event.type == pg.KEYDOWN and event.key == pg.K_f:
if not fullscreen:
print("Changing to FULLSCREEN")
pg.display.set_mode(
(640, 480), pg.OPENGL | pg.DOUBLEBUF | pg.FULLSCREEN
)
else:
print("Changing to windowed mode")
pg.display.set_mode((640, 480), pg.OPENGL | pg.DOUBLEBUF)
fullscreen = not fullscreen
if gl_version[0] >= 4 or (gl_version[0] == 3 and gl_version[1] >= 2):
gpu, f_indices, o_indices = init_gl_modern(display_size)
rotation = Rotation()
else:
init_gl_stuff_old()
if USE_MODERN_GL:
draw_cube_modern(gpu, f_indices, o_indices, rotation)
else:
# clear screen and move camera
GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
# orbit camera around by 1 degree
GL.glRotatef(1, 0, 1, 0)
drawcube_old()
pg.display.flip()
pg.time.wait(10)
pg.quit()
if __name__ == "__main__":
main()