I'm attempting to create a Rubik's Cube in Python, i have gotten as far as visually representing the cube. Struggling a bit with how to implement rotation.
I guess i'm asking for feedback as to how to go about doing this. I thought at first of, rotating each cubes set of vertices's, without much luck.
I basically want to select a slice from an array of cube objects (of varying size), perform a rotation and a translation on each object.
import pygame
import random
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
vertices = (
(1, -1, -1),
(1, 1, -1),
(-1, 1, -1),
(-1, -1, -1),
(1, -1, 1),
(1, 1, 1),
(-1, -1, 1),
(-1, 1, 1)
)
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)
)
surfaces = (
(0,1,2,3),
(3,2,7,6),
(6,7,5,4),
(4,5,1,0),
(1,5,7,2),
(4,0,3,6)
)
colors = (
(1,0,0), #Red
(0,1,0), #Green
(1,0.5,0), #Orange
(1,1,0), #Yellow
(1,1,1), #White
(0,0,1), #Blue
)
class Cube():
'''set the vertices edges and surfaces(colored) for a Cube'''
def __init__(self):
'''initiate the display to show the cube'''
pygame.init()
display = (800,600)
pygame.display.set_mode(display, DOUBLEBUF|OPENGL)
glEnable(GL_DEPTH_TEST)
gluPerspective(45, (display[0]/display[1]), 0.1, 50.0)
glTranslatef(1,1, -40)
def setVertices(self, xmove, ymove, zmove):
'''set predefined vertices'''
xValueChange = xmove
yValueChange = ymove
zValueChange = zmove
newVertices = []
for vert in vertices:
newVert = []
newX = vert[0] + xValueChange
newY = vert[1] + yValueChange
newZ = vert[2] + zValueChange
newVert.append(newX)
newVert.append(newY)
newVert.append(newZ)
newVertices.append(newVert)
return newVertices
def CreateCube(self, vertices):
'''create with OpenGL'''
glBegin(GL_QUADS)
x = 0
for surface in surfaces:
glColor3fv(colors[x])
x+=1
for vertex in surface:
glVertex3fv(vertices[vertex])
glEnd()
class EntireCube():
def __init__(self,typeOfCube):
self.typeOfCube = typeOfCube
self.NewCube = Cube()
def createEntireCube(self):
'''for each dimension x,y,z make a dictionary containing the vertices to be displayed'''
self.cubeDict = {}
count = 0
for x in range(self.typeOfCube):
for y in range(self.typeOfCube):
for z in range(self.typeOfCube):
self.cubeDict[count] = self.NewCube.setVertices(x*2.1,y*2.1,z*2.1)
count += 1
def mainloop(self):
'''key events, creates the matrix of cubes'''
rotateUpKey, rotateDownKey, rotateLeftKey, rotateRightKey = False, False, False, False
rotationalSensitivity = 2
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == KEYDOWN:
if event.key == K_UP:
rotateUpKey = True
if event.key == K_DOWN:
rotateDownKey = True
if event.key == K_LEFT:
rotateLeftKey = True
if event.key == K_RIGHT:
rotateRightKey = True
if event.type == KEYUP:
if event.key == K_UP:
rotateUpKey = False
if event.key == K_DOWN:
rotateDownKey = False
if event.key == K_LEFT:
rotateLeftKey = False
if event.key == K_RIGHT:
rotateRightKey = False
if rotateUpKey:
glRotatef(rotationalSensitivity,-rotationalSensitivity,0,0)
if rotateDownKey:
glRotatef(rotationalSensitivity,rotationalSensitivity,0,0)
if rotateLeftKey:
glRotatef(rotationalSensitivity,0,-rotationalSensitivity,0)
if rotateRightKey:
glRotatef(rotationalSensitivity,0,rotationalSensitivity,0)
#eventually implement keysbindings to call function to rotate a slice of the matrix created
# x = glGetDoublev(GL_MODELVIEW_MATRIX)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
for eachCube in self.cubeDict:
self.NewCube.CreateCube(self.cubeDict[eachCube])
# glPushMatrix()
# glRotatef(1,3,1,1)
# glPopMatrix()
pygame.display.flip()
pygame.time.wait(10)
def main():
NewEntireCube = EntireCube(3) #create a 3x3x3 cube
NewEntireCube.createEntireCube()
NewEntireCube.mainloop()
if __name__ == '__main__':
main()
pygame.quit()
quit()
I'm hoping someone who knows much more about this can give me some guidance as to how to proceed.
A rubik's cube can be organized by an 3 dimensional array of 3x3x3 cubes. It seems to be easy to rotate a slice of the cube, but note if on slice is rotated the positions of the cube change and have to be reorganized. Not only the position changes, also the orientation of the (rotated) single cubes changes.
First of all remove the PyGame and OpenGL initialization form the constructor of the class Cube. That is the wrong place for this. In the following will generate 27 objects of type Cube.
Each cube has to know where it is initially located (self.init_i) and where it is current located after some rotations (self.current_i). This information is encoded in a list with 3 elements, one for each axis. The values are indices of cube in the NxNxN rubik's cube in range [0, N[.
The orientation of a single cube is encoded in 3 dimensional Rotation matrix (self.rot). The rotation matrix has to be initialized by the identity matrix.
class Cube():
def __init__(self, id, N, scale):
self.N = N
self.scale = scale
self.init_i = [*id]
self.current_i = [*id]
self.rot = [[1 if i==j else 0 for i in range(3)] for j in range(3)]
Create a list of the 27 cubes
cr = range(3)
self.cubes = [Cube((x, y, z), 3, scale) for x in cr for y in cr for z in cr]
If a slice of the rubik's cube is rotated, then it has to be checked which of the single cubes is affected. This can be done by checking if the slice matches the entry of the rotation axis of the current position.
def isAffected(self, axis, slice, dir):
return self.current_i[axis] == slice
To rotate a cube, the position and the orientation has to be rotated by 90° around an axis. A 3 dimension rotation matrix consists of 3 direction vectors. A d dimensional vector can be rotated by swapping the coordinates of the vector and inverting the x coordinate of the result for a right rotation and inverting the y coordinate of the result for a left rotation:
rotate right: (x, y) -> (-y, x)
rotate left: (x, y) -> (y, -x)
Since all the vectors of the rotation matrix are in an axis aligned plane this algorithm can be used to change the orientation and the position of the cube. axis the rotation axis (x=0, y=1, z=2) and dir is the rotation direction (1 is right and -1 left)
To rotate the axis vector, 2 components of the vector have to be swapped and one of them inverted.
e.g rotate left around the Y-axis:
(x, y, z) -> (z, y, -x)
When the position is rotated, then the indices have to be swapped. Inverting an index means to map the index i to the index N-1-i:
e.g rotate left around the Y-axis:
(ix, iy, iz) -> (iz, iy, N-1-ix)
Rotation of a single cube:
i, j = (axis+1) % 3, (axis+2) % 3
for k in range(3):
self.rot[k][i], self.rot[k][j] = -self.rot[k][j]*dir, self.rot[k][i]*dir
self.current_i[i], self.current_i[j] = (
self.current_i[j] if dir < 0 else self.N - 1 - self.current_i[j],
self.current_i[i] if dir > 0 else self.N - 1 - self.current_i[i] )
When the cube has to be drawn, then the current position of the cube (self.current_i) and the orientation self.rot can be used to set up 4x4 transformation matrix:
def transformMat(self):
scaleA = [[s*self.scale for s in a] for a in self.rot]
scaleT = [(p-(self.N-1)/2)*2.1*self.scale for p in self.current_i]
return [
*scaleA[0], 0,
*scaleA[1], 0,
*scaleA[2], 0,
*scaleT, 1]
With glPushMatrix respectively glPushMatrix. By glMultMatrix a matrix can be multiplied to the current matrix.
The following function draws a single cube. The parameters angle, axis, slice, dir and it can even apply an animation to the cube, by setting animate=True and setting parameters angle, axis, slice, dir:
def draw(self, col, surf, vert, animate, angle, axis, slice, dir):
glPushMatrix()
if animate and self.isAffected(axis, slice, dir):
glRotatef( angle*dir, *[1 if i==axis else 0 for i in range(3)] )
glMultMatrixf( self.transformMat() )
glBegin(GL_QUADS)
for i in range(len(surf)):
glColor3fv(colors[i])
for j in surf[i]:
glVertex3fv(vertices[j])
glEnd()
glPopMatrix()
To draw the cubes, it is sufficient to call the method draw in a loop:
for cube in self.cubes:
cube.draw(colors, surfaces, vertices, animate, animate_ang, *action)
The implementation of the class Cube works for any NxNxN Rubik's cube.
See the example program for a 3x3x3 cube. The slices of the cube are rotated to the right by the keys 1 to 9 and to the left by the keys F1 to F9:
Of course the code uses the Legacy OpenGL in regard to your original code. But the method Cube.transformMat sets a general 4x4 model matrix for a single partial cube. Thus it is possible to port this code to modern OpenGL with ease.
import pygame
import random
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
vertices = (
( 1, -1, -1), ( 1, 1, -1), (-1, 1, -1), (-1, -1, -1),
( 1, -1, 1), ( 1, 1, 1), (-1, -1, 1), (-1, 1, 1)
)
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))
surfaces = ((0, 1, 2, 3), (3, 2, 7, 6), (6, 7, 5, 4), (4, 5, 1, 0), (1, 5, 7, 2), (4, 0, 3, 6))
colors = ((1, 0, 0), (0, 1, 0), (1, 0.5, 0), (1, 1, 0), (1, 1, 1), (0, 0, 1))
class Cube():
def __init__(self, id, N, scale):
self.N = N
self.scale = scale
self.init_i = [*id]
self.current_i = [*id]
self.rot = [[1 if i==j else 0 for i in range(3)] for j in range(3)]
def isAffected(self, axis, slice, dir):
return self.current_i[axis] == slice
def update(self, axis, slice, dir):
if not self.isAffected(axis, slice, dir):
return
i, j = (axis+1) % 3, (axis+2) % 3
for k in range(3):
self.rot[k][i], self.rot[k][j] = -self.rot[k][j]*dir, self.rot[k][i]*dir
self.current_i[i], self.current_i[j] = (
self.current_i[j] if dir < 0 else self.N - 1 - self.current_i[j],
self.current_i[i] if dir > 0 else self.N - 1 - self.current_i[i] )
def transformMat(self):
scaleA = [[s*self.scale for s in a] for a in self.rot]
scaleT = [(p-(self.N-1)/2)*2.1*self.scale for p in self.current_i]
return [*scaleA[0], 0, *scaleA[1], 0, *scaleA[2], 0, *scaleT, 1]
def draw(self, col, surf, vert, animate, angle, axis, slice, dir):
glPushMatrix()
if animate and self.isAffected(axis, slice, dir):
glRotatef( angle*dir, *[1 if i==axis else 0 for i in range(3)] )
glMultMatrixf( self.transformMat() )
glBegin(GL_QUADS)
for i in range(len(surf)):
glColor3fv(colors[i])
for j in surf[i]:
glVertex3fv(vertices[j])
glEnd()
glPopMatrix()
class EntireCube():
def __init__(self, N, scale):
self.N = N
cr = range(self.N)
self.cubes = [Cube((x, y, z), self.N, scale) for x in cr for y in cr for z in cr]
def mainloop(self):
rot_cube_map = { K_UP: (-1, 0), K_DOWN: (1, 0), K_LEFT: (0, -1), K_RIGHT: (0, 1)}
rot_slice_map = {
K_1: (0, 0, 1), K_2: (0, 1, 1), K_3: (0, 2, 1), K_4: (1, 0, 1), K_5: (1, 1, 1),
K_6: (1, 2, 1), K_7: (2, 0, 1), K_8: (2, 1, 1), K_9: (2, 2, 1),
K_F1: (0, 0, -1), K_F2: (0, 1, -1), K_F3: (0, 2, -1), K_F4: (1, 0, -1), K_F5: (1, 1, -1),
K_F6: (1, 2, -1), K_F7: (2, 0, -1), K_F8: (2, 1, -1), K_F9: (2, 2, -1),
}
ang_x, ang_y, rot_cube = 0, 0, (0, 0)
animate, animate_ang, animate_speed = False, 0, 5
action = (0, 0, 0)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == KEYDOWN:
if event.key in rot_cube_map:
rot_cube = rot_cube_map[event.key]
if not animate and event.key in rot_slice_map:
animate, action = True, rot_slice_map[event.key]
if event.type == KEYUP:
if event.key in rot_cube_map:
rot_cube = (0, 0)
ang_x += rot_cube[0]*2
ang_y += rot_cube[1]*2
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glTranslatef(0, 0, -40)
glRotatef(ang_y, 0, 1, 0)
glRotatef(ang_x, 1, 0, 0)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
if animate:
if animate_ang >= 90:
for cube in self.cubes:
cube.update(*action)
animate, animate_ang = False, 0
for cube in self.cubes:
cube.draw(colors, surfaces, vertices, animate, animate_ang, *action)
if animate:
animate_ang += animate_speed
pygame.display.flip()
pygame.time.wait(10)
def main():
pygame.init()
display = (800,600)
pygame.display.set_mode(display, DOUBLEBUF|OPENGL)
glEnable(GL_DEPTH_TEST)
glMatrixMode(GL_PROJECTION)
gluPerspective(45, (display[0]/display[1]), 0.1, 50.0)
NewEntireCube = EntireCube(3, 1.5)
NewEntireCube.mainloop()
if __name__ == '__main__':
main()
pygame.quit()
quit()
Related
I'm trying to draw a polygon with DDA line algorithm using pygame.
def Round(a):
return int(a + 0.5)
def mainloop():
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
def Draw():
DrawPoly((100, 100), 6, 100)
pygame.display.flip()
def drawDDA(p1, p2, color=[0, 0, 0]):
x0, y0, x1, y1 = p1[0], p1[1], p2[0], p2[1]
steps = abs(x0-x1) if abs(x0-x1) > abs(y0-y1) else abs(y0-y1)
dx = (x1-x0)/steps
dy = (y1-y0)/steps
x, y = x0, y0
gfxdraw.pixel(screen,Round(x),Round(y),color)
for i in range(int(steps)):
x += dx
y += dy
gfxdraw.pixel(screen,Round(x), Round(y),color)
def DrawPoly(center, n, s, color=[0, 0, 0]):
cx, cy = center[0], center[1]
sideAngle = 360/n
bv1x = cx-s/2
bv1y = cy - (s/2)*(1/math.tan(math.radians(sideAngle/2)))
bv2x = cx+s/2
bv2y = bv1y
drawDDA((bv1x, bv1y), (bv2x, bv2y), color)
for i in range(n-1):
# i want to rotate the coordinate plane about an angle "sideAngle" with (cx,cy) as the center
# code here
drawDDA((bv1x, bv1y), (bv2x, bv2y), color)
size = [640, 720]
os.environ['SDL_VIDEO_CENTERED'] = '0'
pygame.init()
screen = pygame.display.set_mode(size)
screen.fill((255, 255, 255))
Draw()
mainloop()
The idea I tried to execute to draw a polygon is
I'm taking a point as center of the polygon
calculating the coordinates of two consecutive points
draw a line between those two points
rotate the whole plane about the center of the polygon by side angle which is (360 degrees/number of sides of the polygon)
now, draw a line with the same coordinates to get another side of the polygon
repeat 4 and 5 for n-2 times to get all the remaining side. (n is the number of sides)
I need a way to rotate the coordinate axis about the center of my polygon.
If you can transform Polar coordinates (d, a) to Cartesian coordinate (x, y) with math.sin and math.cos
x = d * cos(a)
y = d * sin(a)
Change the function DrawPoly:
def DrawPoly(center, n, s, color=[0, 0, 0]):
x0, y0 = center[0], center[1]
a = math.radians(360 / n)
d = s / 2 / math.sin(a / 2)
pts = []
for i in range(n+1):
sideAngle = math.radians(360 * i / n)
x = x0 + d * math.cos(sideAngle)
y = y0 + d * math.sin(sideAngle)
pts.append([x, y])
for i in range(n):
drawDDA(pts[i], pts[i+1], color)
Different polygons with the same side length:
def Draw():
DrawPoly((100, 100), 3, 100, (192, 0, 0))
DrawPoly((100, 100), 4, 100, (0, 192, 0))
DrawPoly((100, 100), 5, 100, (0, 0, 192))
DrawPoly((100, 100), 6, 100, (0, 0, 0))
pygame.display.flip()
I am working on a "3D" cube in Python with Turtle. I am trying to find a way so I can make a cursor lock, or just so when you hold right click you can look at the cube from different angles. Since I do not know how to do this, I have a rotating function.
My current code:
import turtle
VERTEXES = [(-1, -1, -1), ( 1, -1, -1), ( 1, 1, -1), (-1, 1, -1),
(-1, -1, 1), ( 1, -1, 1), ( 1, 1, 1), (-1, 1, 1)]
TRIANGLES = [
(0, 1, 2), (2, 3, 0),
(0, 4, 5), (5, 1, 0),
(0, 4, 3), (4, 7, 3),
(5, 4, 7), (7, 6, 5),
(7, 6, 3), (6, 2, 3),
(5, 1, 2), (2, 6, 5)
]
FOV = 400
# Create turtle,
pointer = turtle.Turtle()
# Turn off move time, makes drawing instant,
turtle.tracer(0, 0)
pointer.up()
def rotate(x, y, r):
s, c = sin(r), cos(r)
return x * c - y * s, x * s + y * c
counter = 0
while True:
# Clear screen,
pointer.clear()
# Draw,
for triangle in TRIANGLES:
points = []
for vertex in triangle:
# Get the X, Y, Z coords out of the vertex iterator,
x, y, z = VERTEXES[vertex]
# Rotate,
x, z = rotate(x, z, counter)
y, z = rotate(y, z, counter)
x, y = rotate(x, y, counter)
# Perspective formula,
z += 5
f = FOV / z
sx, sy = x * f, y * f
# Add point,
points.append((sx, sy))
# Draw triangle,
pointer.goto(points[0][0], points[0][1])
pointer.down()
pointer.goto(points[1][0], points[1][1])
pointer.goto(points[2][0], points[2][1])
pointer.goto(points[0][0], points[0][1])
pointer.up()
# Update,
turtle.update()
counter += 0.01
First off I just wanna say that Turtle does not have a lot of support for mouse events and other fun things that other frameworks have.
Because of turtles lack of support for mouse up events when it is triggered via screen events this will allow you to right click and not hold it down and it will continue to get the delta of your mouse movements before setting the mouse back to the original position. Once you done dragging the mouse just right click again and it will release the mouse.
The delta will be low, usually between 1-3. If this is to low then add in a time.sleep(1/60). This will lock the fps to 60 frames and will allow the mouse to move more before being locked back to its original position.
I also want to show off this question cause it seems like it has a way of adding in on mouse up and move events to turtle but ill let you look at it to decide if you want to go that route.
Find the cursor's current position in Python turtle
Good luck.
from ctypes import windll, Structure, c_long, byref
from turtle import Turtle, Screen, update
is_dragging = False
start_mouse_pos = (0, 0)
class POINT(Structure):
_fields_ = [("x", c_long), ("y", c_long)]
x: int
y: int
def getMousePosition():
pt = POINT()
windll.user32.GetCursorPos(byref(pt))
return (pt.x, pt.y)
def setMousePosition(pos: tuple):
windll.user32.SetCursorPos(int(pos[0]), int(pos[1]))
def onScreenClick(x, y):
global is_dragging, start_mouse_pos
is_dragging = not is_dragging
start_mouse_pos = getMousePosition()
turtle = Turtle()
screen = Screen()
screen.onscreenclick(onScreenClick, 3)
while True:
if is_dragging:
pos = getMousePosition()
delta_pos = (start_mouse_pos[0] - pos[0], start_mouse_pos[1] - pos[1])
# Do something with how much the mouse has moved then set the mouse position back
setMousePosition(start_mouse_pos)
update()
You should be able to do this with an ondrag() event handler. Below is a rework of your code to remove the automatic movement and substitute the ability to manually move the cube by clicking on the the red corner and moving the mouse while continuing to hold the button down (dragging):
from math import sin, cos, atan2 as atan
from turtle import Screen, Turtle
VERTEXES = [
(-1, -1, -1), (1, -1, -1), (1, 1, -1), (-1, 1, -1),
(-1, -1, 1), (1, -1, 1), (1, 1, 1), (-1, 1, 1)
]
TRIANGLES = [
(0, 1, 2),
(2, 3, 0),
(0, 4, 5),
(5, 1, 0),
(0, 4, 3),
(4, 7, 3),
(5, 4, 7),
(7, 6, 5),
(7, 6, 3),
(6, 2, 3),
(5, 1, 2),
(2, 6, 5)
]
FOV = 400
def rotate(x, y, r):
s, c = sin(r), cos(r)
return x * c - y * s, x * s + y * c
orientation = 0 # radians
def draw():
global orientation
turtle.clear()
for triangle in TRIANGLES:
for flag, vertex in enumerate(triangle):
# Get the X, Y, Z coords out of the vertex iterator
x, y, z = VERTEXES[vertex]
# Rotate
x, z = rotate(x, z, orientation)
y, z = rotate(y, z, orientation)
x, y = rotate(x, y, orientation)
# Perspective formula
z += 5
f = FOV / z
s = x * f, y * f
# Draw line
turtle.setposition(s)
turtle.pendown()
if not flag:
start = s
turtle.setposition(start)
turtle.penup()
screen.update()
def orient(x, y):
global orientation
turtle.ondrag(None) # disable handler inside handler
orientation = atan(y, x)
draw()
turtle.ondrag(orient) # reenable handler
# Turn off move time, makes drawing instant,
screen = Screen()
screen.tracer(False)
# Create turtle,
turtle = Turtle('circle')
turtle.fillcolor('red')
turtle.shapesize(0.5)
turtle.penup()
draw()
turtle.ondrag(orient)
screen.mainloop()
The mouse position to cube position mapping clearly isn't optimal, but should be enough to convince you it is possible and provide a starting point.
I'm trying to create a simple 3d rendering of a cube. As in this video from the Coding Train: https://www.youtube.com/watch?v=p4Iz0XJY-Qk on minute 14. I got stuck at one point. Since I'm pretty new to all of this, I'm not exactly sure what's causing my issue. When I start the project, the cube rotates as I want it to, but moves away from the screen to the left and it looks like it's making a circle.
import pygame
import numpy as np
import os
import math
WHITE = (255,255,255)
width, height = 700, 700
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
points = []
angle = 0
points.append(np.array([[300], [250], [1]]))
points.append(np.array([[300], [350], [1]]))
points.append(np.array([[400], [250], [1]]))
points.append(np.array([[400], [350], [1]]))
projectionMatrix = np.array([[1, 0, 0],
[0, 1, 0]])
while True:
clock.tick(30)
screen.fill((0,0,0))
rotation = np.array([[math.cos(angle), -math.sin(angle)],
[math.sin(angle), math.cos(angle)]])
for event in pygame.event.get():
if event.type == pygame.QUIT:
os._exit(1)
for point in points:
projected2d = np.dot(projectionMatrix, point)
rotated = np.dot(rotation, projected2d)
pygame.draw.circle(screen, WHITE, (int(rotated[0][0]), int(rotated[1][0])), 5)
angle += 0.01
pygame.display.update()
I would really appreciate any help to why this is happening and how I could fix it so it's just rotating around.
There is no bug in this code. The points rotate around the top left (0, 0). Note, In 3D mode, p5.js uses a different coordinate system than pygame.
If you want to rotate the dots around the centre of the window, then define the points in range [-1, 1] (normalized device space:
points.append(np.matrix([ 0.5, 0.5, 1]))
points.append(np.matrix([ 0.5, -0.5, 1]))
points.append(np.matrix([-0.5, 0.5, 1]))
points.append(np.matrix([-0.5, -0.5, 1]))
Define a projection matrix from the range[-1, 1] to window space:
projectionMatrix = np.matrix([[height/2, 0, width/2],
[0, height/2, height/2]])
Specify a 3x3 rotation matrix:
rotation = np.array([[math.cos(angle), -math.sin(angle), 0],
[math.sin(angle), math.cos(angle), 0],
[0, 0, 1]])
First rotate the points, then project it to the window:
projected2d = projectionMatrix * rotation * point.reshape((3, 1))
Complete example:
import pygame
import numpy as np
import os
import math
WHITE = (255,255,255)
width, height = 400, 300
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
points = []
angle = 0
points.append(np.matrix([ 0.5, 0.5, 1]))
points.append(np.matrix([ 0.5, -0.5, 1]))
points.append(np.matrix([-0.5, 0.5, 1]))
points.append(np.matrix([-0.5, -0.5, 1]))
projectionMatrix = np.matrix([[height/2, 0, width/2],
[0, height/2, height/2]])
while True:
clock.tick(30)
screen.fill((0,0,0))
rotation = np.matrix([[math.cos(angle), -math.sin(angle), 0],
[math.sin(angle), math.cos(angle), 0],
[0, 0, 1]])
for event in pygame.event.get():
if event.type == pygame.QUIT:
os._exit(1)
for point in points:
projected2d = projectionMatrix * rotation * point.reshape((3, 1))
pygame.draw.circle(screen, WHITE, (int(projected2d[0][0]), int(projected2d[1][0])), 5)
angle += 0.01
pygame.display.update()
I am using PyOpenGl for the first time and watched this YouTube tutorial about it. Here is the video: https://www.youtube.com/watch?v=R4n4NyDG2hI&t=1464s
When I try the code however, pygame opens up and moves one frame and then it freezes and does not stop loading. I am not sure if this is because of the system that I am using or the version of python that I am using.
I have a 11-6 inch 2012 MacBook Air and am using python 2.7.15. The reason that I am using python 2 instead of python 3 is because when I try and pip3 install PyOpenGl, it gives me an error.
import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
verticies = (
(1, -1, -1),
(1, 1, -1),
(-1, 1, -1),
(-1, -1, -1),
(1, -1, 1),
(1, 1, 1),
(-1, -1, 1),
(-1, 1, 1),
)
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 Cube():
global edges
glBegin(GL_LINES)
for edges in edges:
for vertex in edges:
glVertex3fv(verticies[vertex])
glEnd()
def main():
pygame.init()
display = (800,600)
pygame.display.set_mode(display, DOUBLEBUF|OPENGL)
gluPerspective(45, (display[0]/display[1]), 0.1, 50.0)
glTranslatef(0.0, 0.0, -5)
glRotatef(20, 3, 1, 1)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
Cube()
pygame.display.flip()
pygame.time.wait(10)
main()
Another thing that happens is that when I run the program, IDLE gives me this error message:
Traceback (most recent call last):
File "/Users/SplatM4n/Desktop/First3DGraphics.py", line 73, in <module>
main()
File "/Users/SplatM4n/Desktop/First3DGraphics.py", line 66, in main
Cube()
File "/Users/SplatM4n/Desktop/First3DGraphics.py", line 44, in Cube
for vertex in edges:
TypeError: 'int' object is not iterable
I also have a feeling that this is part of the problem so please any kind of help is appriciated.
Thanks, SplatM4n
As shown in the tutorial video, the rotation matrix has to be applied inside the main loop.
glTranslatef(0.0, 0.0, -5)
while True:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
glRotatef(1, 3, 1, 1)
Note, glRotatef does not only generate a rotation matrix. It also multiplies the current matrix by the new rotation matrix. This causes that the cube is continuously and progressively rotated by 1 degree in every frame.
If the glRotatef is done outside the loop, then only a single rotation is applied to the cube. After that the scene appears to be frozen.
See the example, where I applied the changes to your original application:
import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
verticies = ((1, -1, -1), (1, 1, -1), (-1, 1, -1), (-1, -1, -1),
(1, -1, 1), (1, 1, 1), (-1, -1, 1), (-1, 1, 1))
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 Cube():
global edges
glBegin(GL_LINES)
for edge in edges:
for vertex in edge:
glVertex3fv(verticies[vertex])
glEnd()
def main():
pygame.init()
display = (800,600)
pygame.display.set_mode(display, DOUBLEBUF|OPENGL)
gluPerspective(45, (display[0]/display[1]), 0.1, 50.0)
glTranslatef(0.0, 0.0, -5)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
glRotatef(1, 3, 1, 1) # <--------------- rotate inside the main loop
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
Cube()
pygame.display.flip()
pygame.time.wait(10)
main()
Looks like this error is caused by some minor typos; your Cube() function should be as follows:
def Cube():
global edges
glBegin(GL_LINES)
for edge in edges:
for vertex in edge:
glVertex3fv(verticies[vertex])
glEnd()
Note also that the line global edges isn't required here.
Further, if the line global edges is commented out in the original code, then the function generates an exception:
UnboundLocalError: local variable 'edges' referenced before assignment
So I suspect global edges was added to suppress this error, which it does, but does not fix the root cause.
I am following this tutorial for arcball navigation in 3d:
https://en.wikibooks.org/wiki/OpenGL_Programming/Modern_OpenGL_Tutorial_Arcball
I managed to perform all the steps and the navigation works but I cant seem to comprehend last step in tutorial:
An extra trick is converting the rotation axis from camera coordinates
to object coordinates. It's useful when the camera and object are
placed differently. For instace, if you rotate the object by 90° on
the Y axis ("turn its head" to the right), then perform a vertical
move with your mouse, you make a rotation on the camera X axis, but it
should become a rotation on the Z axis (plane barrel roll) for the
object. By converting the axis in object coordinates, the rotation
will respect that the user work in camera coordinates (WYSIWYG). To
transform from camera to object coordinates, we take the inverse of
the MV matrix (from the MVP matrix triplet).
The problem is that when i turn the model in the first step axis of rotation transform as well and they are not aligned with my "camera view". Of course I would like to keep my rotation axes always aligned with my camera view.
Can someone please give me an advice how to tackle it? In the tutorial there is a code but not much of explanation on what it is actually doing plus I only speak Python.
Thank you,
Jacob
My code:
import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
import math
import os
import numpy as np
size = 30
speed = 500
amplitude_amplificator = 80
color_table = ((1,0,0),
(0,1,0),
(0,0,1),
(1,1,0),
(1,0,1),
(0,1,1),
(1,0.5,0),
(0.5,1,0),
(0.5,1,0.5),
(0,0.5,0)
)
locations = ((0,-975, 0),
(0, 975, 0),
(-1273,-975, 0),
(-1273, 975, 0),
(-2482, -975, 0),
(-2482, 975, 0),
(-3737, -975, 0),
(-3737, 975, 0)
)
lines = ((0,2),
(2, 4),
(4, 6),
(1, 3),
(3, 5),
(5, 7),
(0, 1),
(2, 3),
(4, 5),
(6, 7),
)
amplitudes = ((3.38829249165602, 2.38305866657961, 2.52151563664636),
(5.08487438107113, 2.36432294667884, 3.0843991148654),
(3.44312569856563, 1.23112415468012, 1.29869765112226),
(4.0421066637935, 1.40655294535107, 1.36083778879317),
(3.78074337117764, 0.648255908566916, 0.752239154016233),
(5.08887133464996, 0.607037324785205, 0.543523234321567),
(4.49095206021647, 0.432732677308301, 2.18289872563964),
(5.14707697114171, 0.335119576625248, 2.15666871777855)
)
phases = ((-146.873017352057,0,-95.316526141321),
(-149.008372080797, 5.24886681104675, 78.3075732082314),
(-148.241584335287, 5.54327579087787, -118.279685417256),
(-151.844141596427, 6.48705235395368, -113.246406750217),
(-148.14233553496, 27.9523171503408, 65.8254568277543),
(-157.058723259828, 38.8760924034639, 85.2339573112435),
(-153.417593784393, -120.329988461629, 16.0421535833842),
(-156.779107376825, 83.2350395893582, 10.7592173681729)
)
# DRAW CUBE
def Cube(po,si,co):
POS = (
(po[0]+si, po[1]-si, po[2]-si),
(po[0]+si, po[1]+si, po[2]-si),
(po[0]-si, po[1]+si, po[2]-si),
(po[0]-si, po[1]-si, po[2]-si),
(po[0]+si, po[1]-si, po[2]+si),
(po[0]+si, po[1]+si, po[2]+si),
(po[0]-si, po[1]-si, po[2]+si),
(po[0]-si, po[1]+si, po[2]+si)
)
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)
)
glBegin(GL_LINES)
for edge in edges:
for vertex in edge:
glColor3f(co[0],co[1],co[2])
glVertex3fv(POS[vertex])
glEnd()
#DRAW ORIGINAL SHAPE IN LINES
def Line_orig(po):
glBegin(GL_LINES)
for edge in po:
for vertex in edge:
glVertex3fv(locations[vertex])
glEnd()
#Hemisphere mapping
def map_hemisphere(x,y):
z = math.sqrt(abs(1-math.pow(x,2)-math.pow(y,2)))
return z
# Calculate angle of two spatial vectors
def angle_calculation(a,b):
r = math.degrees(math.acos((np.dot(a, b))/(np.linalg.norm(a)*np.linalg.norm(b))))
return r
def main():
mouse_pressed = 0
pygame.init()
display = (1200,800)
pygame.display.set_mode(display, DOUBLEBUF|OPENGL)
gluPerspective(45, (display[0]/display[1]), 0.1, 30000.0)
glTranslatef(0,0.0,-10000)
#glRotatef(90, 1, 0, 0)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
time = pygame.time.get_ticks()/1000
norm_mouse_pos = (2*pygame.mouse.get_pos()[0]/display[0]-1,2*pygame.mouse.get_pos()[1]/display[1]-1,map_hemisphere(2*pygame.mouse.get_pos()[0]/display[0]-1,2*pygame.mouse.get_pos()[1]/display[1]-1))
if pygame.mouse.get_pressed()[0]==1:
if mouse_pressed == 0:
mouse_pressed = 1
clear = lambda: os.system('cls')
clear()
p1 = (norm_mouse_pos[0],norm_mouse_pos[1],map_hemisphere(norm_mouse_pos[0],norm_mouse_pos[1]))
print(p1)
else:
p2 = (norm_mouse_pos[0],norm_mouse_pos[1],map_hemisphere(norm_mouse_pos[0],norm_mouse_pos[1]))
cist = np.cross(p1, p2)
print(angle_calculation(p1,p2))
glRotatef( angle_calculation(p1,p2) , -cist[0] , cist[1] , cist[2] )
else:
mouse_pressed = 0
# Translation of the model via keyboard handling
keys=pygame.key.get_pressed()
if keys[K_w]:
glTranslatef(0, 100, 0)
if keys[K_s]:
glTranslatef(0, -100, 0)
if keys[K_a]:
glTranslatef(-100, 0, 0)
if keys[K_d]:
glTranslatef(100, 0, 0)
# Drawing the Cubes at Nodes Loactions
for item, el in enumerate(locations):
Cube((el[0] + amplitudes[item][0]*math.sin(time + phases[item][0]*(3.1415927/180))*amplitude_amplificator,
el[1] + amplitudes[item][1]*math.sin(time + phases[item][1]*(3.1415927/180))*amplitude_amplificator,
el[2] + amplitudes[item][2]*math.sin(time + phases[item][2]*(3.1415927/180))*amplitude_amplificator
), size, color_table[item])
# Drawing the Original Shapes (Specified nodes in Lines Tuple)
Line_orig(lines)
# Drawing the Deformed Shape
glBegin(GL_LINES)
for edge in lines:
for vertex in edge:
glVertex3fv((locations[vertex][0] + amplitudes[vertex][0]*math.sin(time + phases[vertex][0]*(3.1415927/180))*amplitude_amplificator,
locations[vertex][1] + amplitudes[vertex][1]*math.sin(time + phases[vertex][1]*(3.1415927/180))*amplitude_amplificator ,
locations[vertex][2] + amplitudes[vertex][2]*math.sin(time + phases[vertex][2]*(3.1415927/180))*amplitude_amplificator,
))
glEnd()
# OpenGL Management
pygame.display.flip()
pygame.time.wait(10)
main()
The problem is that when i turn the model in the first step axis of rotation transform as well and they are not aligned with my "camera view". Of course I would like to keep my rotation axes always aligned with my camera view.
In a rendering, each mesh of the scene usually is transformed by the model matrix, the view matrix and the projection matrix.
Projection matrix:
The projection matrix describes the mapping from 3D points of a scene, to 2D points of the viewport.
View matrix:
The view matrix describes the direction and position from which the scene is looked at. The view matrix transforms from the wolrd space to the view (eye) space.
Model matrix:
The model matrix defines the location, oriantation and the relative size of a mesh in the scene. The model matrix transforms the vertex positions from of the mesh to the world space.
If you want to rotate the szene around an axis in view space, the you have to do the following:
Transform the model by all the rotations and translations that you have done before the new rotation operation.
Apply the new rotation operation.
Apply the view translation
Apply the projection matrix
Size the OpenGL fixed function pipeline has a matrix stack, this operations have to be done in the reverse order.
e.g. See the documentation of glMultMatrix:
glMultMatrix multiplies the current matrix with the one specified using m, and replaces the current matrix with the product.
In OpenGL there is one matrix stack for each matrix mode (See glMatrixMode). The matrix modes are GL_MODELVIEW, GL_PROJECTION, and GL_TEXTURE.
First you have to setup the projection matrix on the separated projection matrix stack:
glMatrixMode( GL_PROJECTION );
gluPerspective(45, (display[0]/display[1]), 0.1, 30000.0)
Next create a model matrix
a = (GLfloat * 16)()
modelMat = glGetFloatv(GL_MODELVIEW_MATRIX, a)
Init the model view matrix in the main loop:
glMatrixMode( GL_MODELVIEW );
glLoadIdentity()
Calcualte the new rotation and translation:
axis = (p2[0]- p1[0], p2[1]- p1[1])
glRotatef( angle_calculation(p1,p2), axis[1], axis[0], 0 )
Multiply the model matrix by the previous model matrix and store the combined model matrix:
glMultMatrixf( modelMat )
modelMat = glGetFloatv(GL_MODELVIEW_MATRIX, a)
Setup the view and apply the new model matrix:
glLoadIdentity()
glTranslatef(0,0.0,-10000)
glMultMatrixf( modelMat )
The final code may look like this:
.....
glMatrixMode( GL_PROJECTION );
gluPerspective(45, (display[0]/display[1]), 0.1, 30000.0)
a = (GLfloat * 16)()
modelMat = glGetFloatv(GL_MODELVIEW_MATRIX, a)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
glMatrixMode( GL_MODELVIEW );
glLoadIdentity()
norm_mouse_pos = (2*pygame.mouse.get_pos()[0]/display[0]-1,2*pygame.mouse.get_pos()[1]/display[1]-1,map_hemisphere(2*pygame.mouse.get_pos()[0]/display[0]-1,2*pygame.mouse.get_pos()[1]/display[1]-1))
if pygame.mouse.get_pressed()[0]==1:
if mouse_pressed == 0:
mouse_pressed = 1
clear = lambda: os.system('cls')
clear()
p1 = (norm_mouse_pos[0],norm_mouse_pos[1],map_hemisphere(norm_mouse_pos[0],norm_mouse_pos[1]))
else:
p2 = (norm_mouse_pos[0],norm_mouse_pos[1],map_hemisphere(norm_mouse_pos[0],norm_mouse_pos[1]))
cist = np.cross(p1, p2)
axis = (p2[0]- p1[0], p2[1]- p1[1])
glRotatef( angle_calculation(p1,p2) , axis[1] , axis[0] , 0 )
else:
mouse_pressed = 0
# Translation of the model via keyboard handling
keys=pygame.key.get_pressed()
if keys[K_w]:
glTranslatef(0, 100, 0)
if keys[K_s]:
glTranslatef(0, -100, 0)
if keys[K_a]:
glTranslatef(-100, 0, 0)
if keys[K_d]:
glTranslatef(100, 0, 0)
glMultMatrixf( modelMat )
modelMat = glGetFloatv(GL_MODELVIEW_MATRIX, a)
glLoadIdentity()
glTranslatef(0,0.0,-10000)
glMultMatrixf( modelMat )
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
.....
The "ValueError: math domain error", which sometimes occurs, is because the arcus cosine of a value is defined only, if the value is in the range [-1, 1]. Clamp the value to this range (min(1,max(cos_a,-1))):
def angle_calculation(a,b):
cos_a = np.dot(a, b) / (np.linalg.norm(a)*np.linalg.norm(b))
r = math.degrees(math.acos( min(1,max(cos_a,-1)) ))
return r