How to find PyGame Window Coordinates of an OpenGL Vertice? - python

I am trying to figure out the coordinates of the vertices of two rectangles in a pygame window that is using OpenGL to create the 3D objects.
import pygame
from pygame.locals import *
import random
from OpenGL.GL import *
from OpenGL.GLU import *
rect1 = [(-5.125,0,-40),(-3.125,0,-40),(-3.125,5,-40),(-5.125,5,-40),]
rect2 = [(3.125,0,-40),(5.125,0,-40),(5.125,5,-40),(3.125,5,-40)]
edges = ((0,1),(1,2),(2,3),(3,0))
#This draws the rectangles edges
def Target():
glBegin(GL_LINES)
for edge in edges:
for vertex in edge:
glVertex3fv(rect1[vertex])
glEnd()
glBegin(GL_LINES)
for edge in edges:
for vertex in edge:
glVertex3fv(rect2[vertex])
glEnd()
def main():
try:
pygame.init()
display = (320,240)
pygame.display.set_mode(display, DOUBLEBUF|OPENGL)
gluPerspective(45, (display[0]/display[1]), .1, 1000)
while True:
#iterates through events to check for quits
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
Target()
pygame.display.flip()
pygame.time.wait(100)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
except Exception as e:
print (e)
main()
How do I grab the coordinates on the pygame window(320,240) of the object?

The projection matrix describes the mapping from 3D points of a scene, to 2D points of the viewport. It transforms from eye space to the clip space, and the coordinates in the clip space are transformed to the normalized device coordinates (NDC) by dividing with the w component of the clip coordinates. The NDC are in range (-1,-1,-1) to (1,1,1).
At Perspective Projection the projection matrix describes the mapping from 3D points in the world as they are seen from of a pinhole camera, to 2D points of the viewport. The eye space coordinates in the camera frustum (a truncated pyramid) are mapped to a cube (the normalized device coordinates).
Perspective Projection Matrix:
r = right, l = left, b = bottom, t = top, n = near, f = far
2*n/(r-l) 0 0 0
0 2*n/(t-b) 0 0
(r+l)/(r-l) (t+b)/(t-b) -(f+n)/(f-n) -1
0 0 -2*f*n/(f-n) 0
where :
aspect = w / h
tanFov = tan( fov_y / 2 );
2 * n / (r-l) = 1 / (tanFov * aspect)
2 * n / (t-b) = 1 / tanFov
Since the projection matrix is defined by the field of view and the aspect ratio it is possible to recover the viewport position with the field of view and the aspect ratio. Provided that it is a symmetrical perspective projection, where the field of view is not dispalced (as in your case).
First you have to transform the mose position to normalized device coordianates:
w = with of the viewport
h = height of the viewport
x = X position of the mouse
y = Y position ot the mouse
ndc_x = 2.0 * x/w - 1.0;
ndc_y = 1.0 - 2.0 * y/h; // invert Y axis
Then you have to converte the normalized device coordinates to view coordinates:
z = z coodinate of the geometry in view space
viewPos.x = -z * ndc_x * aspect * tanFov;
viewPos.y = -z * ndc_y * tanFov;
If you want to check if the mouse hovers over your rectangles, then the code may look like this:
mpos = pygame.mouse.get_pos()
z = 40
ndc = [ 2.0 * mpos[0]/width - 1.0, 1.0 - 2.0 * mpos[1]/height ]
tanFov = math.tan( fov_y * 0.5 * math.pi / 180 )
aspect = width / height
viewPos = [z * ndc[0] * aspect * tanFov, z * ndc[1] * tanFov ]
onRect1 = 1 if (viewPos[0]>=rect1[0][0] and viewPos[0]<=rect1[1][0] and viewPos[1]>=rect1[0][1] and viewPos[1]<=rect1[2][1] ) else 0
onRect2 = 1 if (viewPos[0]>=rect2[0][0] and viewPos[0]<=rect2[1][0] and viewPos[1]>=rect2[0][1] and viewPos[1]<=rect2[2][1] ) else 0
See further:
How to recover view space position given view space depth value and ndc xy
Is it possble get which surface of cube will be click in OpenGL?
OpenGL - Mouse coordinates to Space coordinates
In the following I added the algorithm to your example. If the mouse hovers over an rectangle, then the rectangle is colored in red.
import pygame
from pygame.locals import *
import random
from OpenGL.GL import *
from OpenGL.GLU import *
import math
rect1 = [(-5.125,0,-40),(-3.125,0,-40),(-3.125,5,-40),(-5.125,5,-40),]
rect2 = [(3.125,0,-40),(5.125,0,-40),(5.125,5,-40),(3.125,5,-40)]
edges = ((0,1),(1,2),(2,3),(3,0))
fov_y = 45
width = 320
height = 200
#This draws the rectangles edges
def Target():
mpos = pygame.mouse.get_pos()
z = 40
ndc = [ 2.0 * mpos[0]/width - 1.0, 1.0 - 2.0 * mpos[1]/height ]
tanFov = math.tan( fov_y * 0.5 * math.pi / 180 )
aspect = width / height
viewPos = [z * ndc[0] * aspect * tanFov, z * ndc[1] * tanFov ]
onRect1 = 1 if (viewPos[0]>=rect1[0][0] and viewPos[0]<=rect1[1][0] and viewPos[1]>=rect1[0][1] and viewPos[1]<=rect1[2][1] ) else 0
onRect2 = 1 if (viewPos[0]>=rect2[0][0] and viewPos[0]<=rect2[1][0] and viewPos[1]>=rect2[0][1] and viewPos[1]<=rect2[2][1] ) else 0
glColor3f( 1, 1-onRect1, 1-onRect1 )
glBegin(GL_LINES)
for edge in edges:
for vertex in edge:
glVertex3fv(rect1[vertex])
glEnd()
glColor3f( 1, 1-onRect2, 1-onRect2 )
glBegin(GL_LINES)
for edge in edges:
for vertex in edge:
glVertex3fv(rect2[vertex])
glEnd()
def main():
try:
pygame.init()
display = (width,height)
pygame.display.set_mode(display, DOUBLEBUF|OPENGL)
glMatrixMode(GL_PROJECTION)
gluPerspective(fov_y, (display[0]/display[1]), .1, 1000)
glMatrixMode(GL_MODELVIEW)
while True:
#iterates through events to check for quits
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
Target()
pygame.display.flip()
pygame.time.wait(100)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
except Exception as e:
print (e)
main()
Extension to the answer
Of course you can also do it the other way around. You can transform the corner points of the rectangle to normalized device coordinates and compare them to the mouse position, in normalized device coordinates.
For this you have to read the projection matrix by glGetFloatv(GL_PROJECTION_MATRIX):
prjMat = (GLfloat * 16)()
glGetFloatv(GL_PROJECTION_MATRIX, prjMat)
And you need a function which transform a 3 dimensional cartesian vector by a projection matrix. This is done by multiplying the vector by the projection matrix, which gives homogeneous clip space coordinates. The normalized device coordinates are calculated by dividing the x, y, and z component by the w component.
def TransformVec3(vecA,mat44):
vecB = [0, 0, 0, 0]
for i0 in range(0, 4):
vecB[i0] = vecA[0] * mat44[0*4+i0] + vecA[1] * mat44[1*4+i0] + vecA[2] * mat44[2*4+i0] + mat44[3*4+i0]
return [vecB[0]/vecB[3], vecB[1]/vecB[3], vecB[2]/vecB[3]]
The following function tests if the mouse position is in an rectangle defined by a lower left and a upper right point (the corner points have to be in view space coordinates):
def TestRec(prjMat, mpos, ll, tr):
ll_ndc = TransformVec3(ll, prjMat)
tr_ndc = TransformVec3(tr, prjMat)
ndc = [ 2.0 * mpos[0]/width - 1.0, 1.0 - 2.0 * mpos[1]/height ]
inRect = 1 if (ndc[0]>=ll_ndc[0] and ndc[0]<=tr_ndc[0] and ndc[1]>=ll_ndc[1] and ndc[1]<=tr_ndc[1] ) else 0
return inRect
Again I added the algorithm to your example. If the mouse hovers over an rectangle, then the rectangle is colored in red.
import pygame
from pygame.locals import *
import random
from OpenGL.GL import *
from OpenGL.GLU import *
import math
rect1 = [(-5.125,0,-40),(-3.125,0,-40),(-3.125,5,-40),(-5.125,5,-40),]
rect2 = [(3.125,0,-40),(5.125,0,-40),(5.125,5,-40),(3.125,5,-40)]
edges = ((0,1),(1,2),(2,3),(3,0))
fov_y = 45
width = 320
height = 200
def TransformVec3(vecA,mat44):
vecB = [0, 0, 0, 0]
for i0 in range(0, 4):
vecB[i0] = vecA[0] * mat44[0*4+i0] + vecA[1] * mat44[1*4+i0] + vecA[2] * mat44[2*4+i0] + mat44[3*4+i0]
return [vecB[0]/vecB[3], vecB[1]/vecB[3], vecB[2]/vecB[3]]
def TestRec(prjMat, mpos, ll, tr):
ll_ndc = TransformVec3(ll, prjMat)
tr_ndc = TransformVec3(tr, prjMat)
ndc = [ 2.0 * mpos[0]/width - 1.0, 1.0 - 2.0 * mpos[1]/height ]
inRect = 1 if (ndc[0]>=ll_ndc[0] and ndc[0]<=tr_ndc[0] and ndc[1]>=ll_ndc[1] and ndc[1]<=tr_ndc[1] ) else 0
return inRect
#This draws the rectangles edges
def Target():
prjMat = (GLfloat * 16)()
glGetFloatv(GL_PROJECTION_MATRIX, prjMat)
mpos = pygame.mouse.get_pos()
onRect1 = TestRec(prjMat, mpos, rect1[0], rect1[2])
onRect2 = TestRec(prjMat, mpos, rect2[0], rect2[2])
glColor3f( 1, 1-onRect1, 1-onRect1 )
glBegin(GL_LINES)
for edge in edges:
for vertex in edge:
glVertex3fv(rect1[vertex])
glEnd()
glColor3f( 1, 1-onRect2, 1-onRect2 )
glBegin(GL_LINES)
for edge in edges:
for vertex in edge:
glVertex3fv(rect2[vertex])
glEnd()
def main():
try:
pygame.init()
display = (width,height)
pygame.display.set_mode(display, DOUBLEBUF|OPENGL)
glMatrixMode(GL_PROJECTION)
gluPerspective(fov_y, (display[0]/display[1]), .1, 1000)
glMatrixMode(GL_MODELVIEW)
while True:
#iterates through events to check for quits
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
Target()
pygame.display.flip()
pygame.time.wait(100)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
except Exception as e:
print (e)
main()

Related

Python 2d Raycaster not using proper depth [duplicate]

This question already has answers here:
How do I fix wall warping in my raycaster?
(1 answer)
Problem with recognising where a ray in raycaster intersects a wall along the horizontal axis
(1 answer)
cant get raycast to work from angles 90 to 270 pygame
(1 answer)
Why my raycasting keeps going through walls?
(1 answer)
Closed 3 months ago.
I am trying to create a raycast visualizer. The lines are supposed to shoot out and stop when they collide with a wall. Currently the length is entirely random and sometimes the rays point in directions that are even behind me. I am using an scale of 47 when i draw things to the screen for tiling purposes. I have tried for 10 or so hours every scale in the raycast code and I can't see what I am missing.
import pygame
import numpy
from PygameEngine import GameEngine
import sys
import math
class RayCasting:
FOV = numpy.pi/5
HALF_FOV = FOV/2
NUM_RAYS = GameEngine.WIDTH//2
HALF_NUM_RAYS = NUM_RAYS//2
DELTA_ANGLE = FOV/NUM_RAYS
MAX_DEPTH = 20
def __init__(self, game):
self.game = game
def rayCast(self):
ox, oy = self.game.wasd.pos
x_map = int(ox)
y_map = int(oy)
ray_angle = self.game.wasd.angle - self.HALF_FOV + 0.000001
for ray in range(self.NUM_RAYS):
sin_a = math.sin(ray_angle)
cos_a = math.cos(ray_angle)
# horizontals
y_hor, dy = (y_map + 1, 1) if sin_a > 0 else (y_map - 1e-6, -1)
depth_hor = (y_hor - oy) / sin_a
x_hor = ox + depth_hor * cos_a
delta_depth = dy / sin_a
dx = delta_depth * cos_a
print("YHor: ",y_hor, " DY:", dy, " Depth Hor: ", depth_hor, "X Hor: ", x_hor,
" Delta Depth: ", delta_depth, " DX: ", dx)
for i in range(self.MAX_DEPTH):
tile_hor = int(x_hor), int(y_hor)
if tile_hor in self.game.MAP.wallMap:
# print("INSIDE HOR")
break
x_hor += dx
y_hor += dy
depth_hor += delta_depth
# verticals
x_vert, dx = (x_map + 1, 1) if cos_a > 0 else (x_map - 1e-6, -1)
depth_vert = (x_vert - ox) / cos_a
y_vert = oy + depth_vert * sin_a
delta_depth = dx / cos_a
dy = delta_depth * sin_a
for i in range(self.MAX_DEPTH):
tile_vert = int(x_vert), int(y_vert)
if tile_vert in self.game.MAP.wallMap:
# print("INSIDE VERT")
break
x_vert += dx
y_vert += dy
depth_vert += delta_depth
# depth, texture offset
if depth_vert < depth_hor:
depth = depth_vert
#y_vert %= 1
#offset = y_vert if cos_a > 0 else (1 - y_vert)
else:
depth = depth_hor
#x_hor %= 1
#offset = (1 - x_hor) if sin_a > 0 else x_hor
# remove fishbowl effect
#depth *= math.cos(self.game.wasd.angle - ray_angle)
# projection
#proj_height = SCREEN_DIST / (depth + 0.0001)
# ray casting result
#self.ray_casting_result.append((depth, proj_height, texture, offset))
ray_angle += self.DELTA_ANGLE
pygame.draw.line(self.game.screen, "yellow", (ox*self.game.CELLSIZE,oy*self.game.CELLSIZE), (ox*self.game.CELLSIZE+depth*cos_a, oy*self.game.CELLSIZE+depth*sin_a), 1)
def update(self):
self.rayCast()
from PygameEngine import GameEngine
from Circle import Circle
import pygame
from pygame.locals import *
import sys
import numpy
from map import Map
from RaycastFunction import RayCasting
class RaycastGame(GameEngine):
# Space bar to place this circle which will connect to the WASD with a line
planet = Circle((0,0,0))
planet.keyX = 5
planet.keyY = 5
# Grid set up
def __init__(self):
super().__init__()
self.load()
self.MAP = Map()
self.CELLSIZE = self.MAP.CELLSIZE
# Circle controllable with WASD
self.wasd = Circle((123, 255, 123))
self.raycast = RayCasting(self)
def DDA(self):
# -
# * |
# Remember the Plane is - --m-- +
# * = target |
# m = mouse +
distX = self.wasd.keyX - self.planet.pos[0]
distY = self.wasd.keyY - self.planet.pos[1]
#hypotenuse = numpy.sqrt(distX**2+distY**2)
theta = numpy.arctan((distY/(distX+.0001)))
theta += numpy.deg2rad(90)
# print(numpy.rad2deg(theta), " THETA")
collisionPos = (0,0)
def draw(self):
# Draw MAP array
self.MAP.drawMap()
self.MAP.drawGrid()
# Draw mouse character
#pygame.draw.circle(self.screen, (0, 0, 0),
#(self.plane), Circle.radius)
# Draw planet
# self.planet.draw(self.screen)
# Draw wasd character
self.wasd.draw(self.screen)
# Connect mouse and wasd characters with a line
#pygame.draw.line(self.screen, (255, 255, 255), self.planet.pos, (self.wasd.keyX, self.wasd.keyY), 5)
def update(self):
self.planet.placePlanet()
self.wasd.move()
self.DDA()
self.raycast.update()
def run(self):
# Game loop.
while True:
#This gets written over. Only for clearing screen before each draw
self.screen.fill((0, 0, 0))
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
# Update.
self.update()
# Draw
self.draw()
pygame.display.flip()
self.fpsClock.tick(self.FPS)
I do not understand why the rays are not stopping in the proper area.

pyglet when using draw() instead of eventloop

I'm trying to draw a circle with pyglet. But it is not visible when I use the draw() function instad of the app.run() loop. Any suggestions what I can do? thanks
from math import *
from pyglet.gl import *
window = pyglet.window.Window()
def makeCircle(x_pos, y_pos, radius, numPoints):
verts = []
glClear(pyglet.gl.GL_COLOR_BUFFER_BIT)
glColor3f(1,1,0)
for i in range(numPoints):
angle = radians(float(i)/numPoints * 360.0)
x = radius *cos(angle) + x_pos
y = radius *sin(angle) + y_pos
verts += [x,y]
circle = pyglet.graphics.vertex_list(numPoints, ('v2f', verts))
circle.draw(GL_LINE_LOOP)
input()
makeCircle(5,5, 100, 10)
You've to call window.flip() to update the window.
Since you don't have set a projection matrix, the geometry has to be draw in normalized device coordinates, which are in range [-1, 1] for all 3 components (x, y, z). Note, pyglet set a projection matrix by default when the application is started by pyglet.app.run().
Call window.flip() and change the geometry:
from math import *
from pyglet.gl import *
window = pyglet.window.Window()
def makeCircle(x_pos, y_pos, radius, numPoints):
verts = []
glClear(pyglet.gl.GL_COLOR_BUFFER_BIT)
glColor3f(1,1,0)
for i in range(numPoints):
angle = radians(float(i)/numPoints * 360.0)
x = radius *cos(angle) + x_pos
y = radius *sin(angle) + y_pos
verts += [x,y]
circle = pyglet.graphics.vertex_list(numPoints, ('v2f', verts))
circle.draw(GL_LINE_LOOP)
window.flip() # <--------
input()
makeCircle(0, 0, 0.5, 10) # <--------
Alternatively you can set an orthographic projection on your own, by glOrtho. e.g.:
from math import *
from pyglet.gl import *
window = pyglet.window.Window()
def makeCircle(x_pos, y_pos, radius, numPoints):
verts = []
glMatrixMode(GL_PROJECTION)
glOrtho(0, 640, 0, 480, -1, 1)
glMatrixMode(GL_MODELVIEW)
glClear(pyglet.gl.GL_COLOR_BUFFER_BIT)
glColor3f(1,1,0)
for i in range(numPoints):
angle = radians(float(i)/numPoints * 360.0)
x = radius *cos(angle) + x_pos
y = radius *sin(angle) + y_pos
verts += [x,y]
circle = pyglet.graphics.vertex_list(numPoints, ('v2f', verts))
circle.draw(GL_LINE_LOOP)
text = 'This is a test but it is not visible'
label = pyglet.text.Label(text, font_size=36,
x=10, y=10, anchor_x='left', anchor_y='bottom',
color=(255, 123, 255, 255))
label.draw()
window.flip()
input()
makeCircle(5,5, 100, 10)

Solar System in OpenGL, Camera position

I want to make simple solar system in OpenGL, with four cameras.
What I want is simple, just locate a camera at earth's one side.
In follow code, I get MODELVIEW_MATRIX by glGetFloatv(GL_MODELVIEW_MATRIX) (line 116)
So I thought that { MODELVIEW_MATRIX multiple [[0],[0],[0],[1]] matrix } get a Origin point of planet in world coordinate system.
But it doesn't work well and so I need some help.
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
import math
import numpy as np
WINDOW_WIDTH = 600
WINDOW_HEIGHT = 600
WINDOW_POSITION_X = 0
WINDOW_POSITION_Y = 0
earthRevolveAngle = 180
earthRotateAngle = 0
satelliteRevolveAngle = 180
satellitePlaneAngle = 0
plutoRevolveAngle = 180
plutoRotateAngle = 0
plutoCamera = np.array([0, 0, 0])
earthPosition = np.array([0, 0, 0])
class Camera :
def __init__(self): #constructor
self.loc = np.array([0.0, 50.0, 0.0])
self.tar = np.array([0.0, 0.0, 0.0])
self.up = np.array([1.0, 0.0, 0.0])
self.right = np.array([1.0, 0.0, 0.0])
self.dir = np.array([0.0, 0.0, -1.0])
self.asp = 1.0
self.fov = 70
self.near= 0.1
self.far = 500.0
def setCameraLoc(self, loc):
self.loc = loc
self.tar = self.loc + self.dir
def setCamera(self, loc, tar, up):
self.loc, self.tar, self.up = loc, tar, up
self.dir = self.tar - self.loc
l = np.linalg.norm(self.dir)
if l > 0.0 :
self.dir = self.dir / l
l = np.linalg.norm(self.up)
if l > 0.0 :
self.up = self.up / l
self.right = np.cross(self.dir, self.up)
def setLens(self, fov, asp, near, far):
self.fov, self.asp, self.near, self.far = fov, asp, near, far
def applyCamera(self):
gluLookAt(self.loc[0], self.loc[1], self.loc[2],
self.tar[0], self.tar[1], self.tar[2],
self.up [0], self.up [1], self.up [2])
def applyLens(self):
gluPerspective(self.fov, self.asp, self.near, self.far)
def moveForward(self, step=1.0):
self.tar += self.dir*step
self.loc += self.dir*step
def zoomIn(self, step=1.0):
self.loc += self.dir*step
def zoomOut(self, step=1.0):
self.loc -= self.dir*step
def drawPlanet(semiMajor, semiMinor, revolveAngle, rotateAngle, shape, slope, axisTilt) :
global plutoCamera, earthPosition
a = semiMajor
b = semiMinor
#Orbit's slope
glRotatef(slope, 1, 0, 0)
#Start draw orbit
glBegin(GL_LINE_STRIP)
for i in range(0, 361):
theta = 2.0 * 3.141592 * i / 360.0
x = a*math.cos(theta)
z = b*math.sin(theta)
glVertex3f(x, 0, z)
glEnd()
#End draw orbit
theta = 2.0 * 3.141592 * (revolveAngle%360) / 360.0
x = a * math.cos(theta)
z = b * math.sin(theta)
glRotatef(revolveAngle, 0, 1, 0)
glTranslatef( math.sqrt( x**2 + z**2 ) , 0, 0)
glRotatef(rotateAngle, 0, 1, 0)
glRotatef(axisTilt, 0, 0, 1)
t = glGetFloatv(GL_MODELVIEW_MATRIX)
if(shape == "satellite"):
glScalef(0.4,0.4,0.4)
glutSolidTetrahedron()
glScalef(2.5,2.5,2.5)
elif(shape == "earth"):
glutWireCube(1)
earthPosition = t * np.matrix( [[0],[0],[0],[1]] )
elif(shape == "pluto"):
glScalef(0.4,0.4,0.4)
glutWireOctahedron()
glScalef(2.5,2.5,2.5)
def drawScene() :
global earthRevolveAngle, earthRotateAngle, satelliteAngle, satelliteRevolveAngle, satellitePlaneAngle, plutoRevolveAngle, plutoRotateAngle
# draw solar
glColor3f(1,0,0)
glutWireSphere(1.0, 20, 20)
glPushMatrix()
# draw earth
glColor3f(0,0.5,1.0)
earthRevolveAngle+=0.05 # earth's revolution
earthRotateAngle+=0.2
drawPlanet(5, 5, earthRevolveAngle, earthRotateAngle, "earth",0,15)
# draw satellite
glColor3f(0.7,0.7,0.7)
satelliteRevolveAngle+=1.5
satellitePlaneAngle += 0.25
glRotatef(satellitePlaneAngle, 1, 0, 0)
drawPlanet(1, 1, satelliteRevolveAngle, 1, "satellite",0,0)
# draw pluto
glPopMatrix() # comeback to solar central coordinate
glPushMatrix()
glColor3f(0.9,0.7,0.26)
plutoRevolveAngle+=0.0125 # pluto's revolution
plutoRotateAngle+=0.1 # pluto's rotation
drawPlanet(10, 8, plutoRevolveAngle,plutoRotateAngle, "pluto",0,0)
glPopMatrix()
Cam = Camera()
def disp() :
global plutoCamera, earthPosition, Cam
# reset buffer
glClear(GL_COLOR_BUFFER_BIT)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
# Camera view setting
Cam.setLens(30,1.0,0.1,1000)
Cam.applyLens()
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
# first quadrant
glViewport(int(WINDOW_POSITION_X+WINDOW_WIDTH/2), int(WINDOW_POSITION_Y + WINDOW_HEIGHT/2), int(WINDOW_WIDTH/2), int(WINDOW_HEIGHT/2))
glPushMatrix()
Cam.setCamera( np.array([0,0,1]), np.array([0,0,100]), np.array([0,1,0]))
Cam.applyCamera()
drawScene()
glPopMatrix()
# second quadrant
glViewport(int(WINDOW_POSITION_X), int(WINDOW_POSITION_Y + WINDOW_HEIGHT/2), int(WINDOW_WIDTH/2), int(WINDOW_HEIGHT/2) )
glPushMatrix()
Cam.setCamera( np.array([30,30,30]), np.array([0,0,0]), np.array([0,1,0]))
Cam.applyCamera()
drawScene()
glPopMatrix()
# third quadrant
glViewport(WINDOW_POSITION_X, WINDOW_POSITION_Y, int(WINDOW_WIDTH/2) , int(WINDOW_HEIGHT/2) )
glPushMatrix()
Cam.setCamera( plutoCamera, np.array([0,0,0]), np.array([0,1,0]))
Cam.applyCamera()
drawScene()
glPopMatrix()
# fourth quadrant
glViewport(int(WINDOW_POSITION_X+WINDOW_WIDTH/2), WINDOW_POSITION_Y, int(WINDOW_WIDTH/2), int(WINDOW_HEIGHT/2) )
glPushMatrix()
Cam.setCamera( earthPosition, np.array([0,0,0]) , np.array([0,1,0]))
Cam.applyCamera()
drawScene()
glPopMatrix()
glFlush()
def main():
# windowing
glutInit(sys.argv)
glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB)
glutInitWindowSize(WINDOW_WIDTH,WINDOW_HEIGHT)
glutInitWindowPosition(WINDOW_POSITION_X,WINDOW_POSITION_Y)
glutCreateWindow(b"Simple Solar_201624489_ParkChangHae")
glClearColor(0, 0.0, 0.0, 0)
# register callbacks
glutDisplayFunc(disp)
glutIdleFunc(disp)
# enter main infinite-loop
glutMainLoop()
if __name__=="__main__":
main()
The * operator doesn't do what you expect it to do, it is an array multiplication, but not a matrix multiplication. It would perform a componentwise multiplication of the elements. See how does multiplication differ for NumPy Matrix vs Array classes? and Numerical operations on arrays.
Use numpy.dot or numpy.matmul to transform a vector by a matrix.
The result of the transformation of a 4 component vector (Homogeneous coordinates) by 4*4 matrix, is still a 4 component vector. In general you would have to do a perspective divide after the transformation. But the model view matrix is an Orthogonal matrix, so it is sufficient to use the first 3 components of the result, since the 4th component is always 1:
pos = np.array( [0,0,0,1] )
pos = np.dot( pos, t )
earthPosition = pos[0:3]
But note, the view space position of the coordinate (0, 0, 0, 1) is the translation part (the 4th row) of the model view matrix:
earthPosition = t[3][0:3]
Sadly this is not what you want to do, because you want to know the world position of the earth, but not the view position.
Since glGetFloatv(GL_MODELVIEW_MATRIX) returns the model view matrix, the transformation calculates the view position, but not the world position.
You have to transform by the model matrix, but not the model view matrix. Since you can't separated model matrix from the model view matrix, this is not that easy.
What you can get get is the view matrix. With the view matrix and the model view matrix you van get the world position.
A transformation by the model matrix is the same as a transformation by the model view matrix and the inverse view matrix:
p_world = inverse(view_matrix) * model_view_matrix * p_model
I recommend to get the view matrix and to calculate the inverse model view matrix in the Cam class right after setting it by lookAt. The inverse matrix can be calculated by numpy.linalg.inv:
def applyCamera(self):
gluLookAt(self.loc[0], self.loc[1], self.loc[2],
self.tar[0], self.tar[1], self.tar[2],
self.up [0], self.up [1], self.up [2])
self.viewmat = glGetFloatv(GL_MODELVIEW_MATRIX)
self.inv_viewmat = np.linalg.inv(self.viewmat)
Finally the world position is a simple transformation of the 4th row of the model view matrix by the inverse view matrix:
global plutoCamera, earthPosition, Cam
.....
model_view = glGetFloatv(GL_MODELVIEW_MATRIX)
if(shape == "satellite"):
glScalef(0.4,0.4,0.4)
glutSolidTetrahedron()
glScalef(2.5,2.5,2.5)
elif(shape == "earth"):
glutWireCube(1)
pos = np.dot( model_view[3], Cam.inv_viewmat )
earthPosition = pos[0:3]
elif(shape == "pluto"):
glScalef(0.4,0.4,0.4)
glutWireOctahedron()
glScalef(2.5,2.5,2.5)
pos = np.dot( model_view[3], Cam.inv_viewmat )
plutoCamera = pos[0:3]
Preview:

Adding gravity to a bouncing ball using vectors

I have a gravity vector (in the form [r, theta]) which I add to my ball's velocity vector. For some reason, the ball doesn't return to the same height after bouncing, but instead slowly loses height sporadically. I am guessing there's some rounding error or something in a calculation I'm using, but I can't isolate the issue.
Here is my code. You need both files and pygame to run it. Sorry if it's a little confusing. I can comment anything some more if you want.
I added a marker whenever the ball reaches its max height so you guys what I mean. I want the ball to return to exactly the same height every time it bounces.
I took a little bit of unnecessary code out. The full program is under the pastebin links.
https://pastebin.com/FyejMCmg - PhysicsSim
import pygame, sys, math, tools, random, time
from pygame.locals import *
clock = pygame.time.Clock()
lines = []
class Particle:
def __init__(self,screen,colour, mass, loc, vel):
self.screen = screen
self.colour = colour
self.mass = mass
self.x = loc[0]
self.y = loc[1]
self.location = self.x,self.y
self.speed = vel[0]
self.angle = vel[1]
def update(self):
global lines
# add gravity
self.speed,self.angle = tools.add_vectors2([self.speed,self.angle], tools.GRAVITY)
# update position
dt = clock.tick(60)
self.x += self.speed * tools.SCALE * math.cos(self.angle) * dt
self.y -= self.speed * tools.SCALE * math.sin(self.angle) * dt
self.location = int(self.x),int(self.y)
# border checking
do = False
n=[]
if ((self.y+self.mass) > tools.SCREEN_HEIGHT):
self.y = tools.SCREEN_HEIGHT-self.mass
n = [0,1]
do = True
# adds position to array so max height so max height can be recorded
if (self.speed==0):
lines.append([self.screen, self.location, self.mass])
# bounce
if do:
#init, convert everything to cartesian
v = tools.polarToCartesian([self.speed, self.angle])
#final -> initial minus twice the projection onto n, where n is the normal to the surface
a = tools.scalarP(2*abs(tools.dotP(v,n)),n) #vector to be added to v
v = tools.add_vectors(v,a)
self.angle = tools.cartesianToPolar(v)[1] # does not set magnitude
# drawing
pygame.draw.circle(self.screen, self.colour, self.location, self.mass, 0)
# draws max height line
def draw_line(l):
screen = l[0]
location = l[1]
radius = l[2]
pygame.draw.line(screen, tools.BLACK, [location[0] + 15, location[1]-radius],[location[0] - 15, location[1]-radius])
def main():
pygame.init()
DISPLAY = pygame.display.set_mode(tools.SCREEN_SIZE,0,32)
DISPLAY.fill(tools.WHITE)
particles = []
particles.append(Particle(DISPLAY, tools.GREEN, 10, [100,100], [0,0]))
done = False
while not done:
global lines
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
DISPLAY.fill(tools.WHITE)
for i in particles:
i.update()
for l in lines:
draw_line(l)
pygame.display.update()
main()
https://pastebin.com/Epgqka31 - tools
import math
#colours
WHITE = (255, 255, 255)
BLUE = ( 0, 0, 255)
GREEN = ( 0, 255, 0)
RED = ( 255, 0, 0)
BLACK = ( 0, 0, 0)
COLOURS = [WHITE,BLUE,GREEN,RED,BLACK]
#screen
SCREEN_SIZE = SCREEN_WIDTH,SCREEN_HEIGHT = 1000,700
#vectors
GRAVITY = [5.0, 3*math.pi/2] # not 9.8 because it seems too high
SCALE = 0.01
# converts polar coordinates to cartesian coordinates in R2
def polarToCartesian(v):
return [v[0]*math.cos(v[1]), v[0]*math.sin(v[1])]
# converts cartesian coordinates to polar coordinates in R2
def cartesianToPolar(v):
return [math.sqrt(v[0]**2 + v[1]**2), math.atan2(v[1],v[0])]
# dots two cartesian vectors in R2
def dotP(v1, v2):
return v1[0]*v2[0] + v1[1]*v2[1]
# multiplies cartesian vector v by scalar s in Rn
def scalarP(s,v):
v_=[]
for i in v:
v_.append(s*i)
return v_
# returns the sum of two cartesian vectors in R2
def add_vectors(v1, v2):
return [v1[0]+v2[0], v1[1]+v2[1]]
# returns the sum of two polar vectors in R2, equations from https://math.stackexchange.com/questions/1365622/adding-two-polar-vectors
def add_vectors2(v1,v2):
r1,r2,t1,t2 = v1[0],v2[0],v1[1],v2[1]
return [math.sqrt(r1**2 + r2**2 + 2*r1*r2*math.cos(t2-t1)), t1 + math.atan2(r2*math.sin(t2 - t1), r1 + r2*math.cos(t2 - t1))]
Your time interval, dt = clock.tick(60), is not a constant. If you change it to dt = 60 your program runs as expected.
Have a look a the Verlet Algorithm and implement it in your code. You are on the right track!

Rotating a rectangle (not image) in pygame

In pygame I use pygame.draw.rect(screen, color, rectangle) for all the rectangles in my program. I want to be able to rotate these rectangles to any angle. I have seen the following code to rotate IMAGES but my question is with RECTANGLES.
pygame.transform.rotate(image, angle)
But I am working with rectangles, I don't have an image or "surface" that I can rotate. When I try to rotate a rectangle with
rect = pygame.draw.rect(screen, self.color, self.get_rectang())
rotatedRect = pygame.transform.rotate(rect, self.rotation)
screen.blit(rotatedRect)
This gives TypeError: must be pygame.Surface, not pygame.Rect on the line with .rotate()
My question is, how can I rotate a and display a RECTANGLE(x,y,w,h), not an image, in pygame.
The linked post that this is a "potential duplicate" of is not a duplicate. One answer explains about the consequences of rotating a rectangle and the other uses code for rotating an image.
See the second answer here: Rotating a point about another point (2D)
I think rectangles can only be horiz or vertical in their oreintation. You need to define the corners and rotate them and then draw and fill between them.
The other way is to make a class
class myRect(pygame.Surface):
def __init__(self, parent, xpos, ypos, width, height):
super(myRect, self).__init__(width, height)
self.xpos = xpos
self.ypos = ypos
self.parent = parent
def update(self, parent):
parent.blit(self, (self.xpos, self.ypos))
def rotate(self, angle):
#(your rotation code goes here)
and use that instead, as then you will be able to rotate it using the transform function.
import pygame as py
# define constants
WIDTH = 500
HEIGHT = 500
FPS = 30
# define colors
BLACK = (0 , 0 , 0)
GREEN = (0 , 255 , 0)
# initialize pygame and create screen
py.init()
screen = py.display.set_mode((WIDTH , HEIGHT))
# for setting FPS
clock = py.time.Clock()
rot = 0
rot_speed = 2
# define a surface (RECTANGLE)
image_orig = py.Surface((100 , 100))
# for making transparent background while rotating an image
image_orig.set_colorkey(BLACK)
# fill the rectangle / surface with green color
image_orig.fill(GREEN)
# creating a copy of orignal image for smooth rotation
image = image_orig.copy()
image.set_colorkey(BLACK)
# define rect for placing the rectangle at the desired position
rect = image.get_rect()
rect.center = (WIDTH // 2 , HEIGHT // 2)
# keep rotating the rectangle until running is set to False
running = True
while running:
# set FPS
clock.tick(FPS)
# clear the screen every time before drawing new objects
screen.fill(BLACK)
# check for the exit
for event in py.event.get():
if event.type == py.QUIT:
running = False
# making a copy of the old center of the rectangle
old_center = rect.center
# defining angle of the rotation
rot = (rot + rot_speed) % 360
# rotating the orignal image
new_image = py.transform.rotate(image_orig , rot)
rect = new_image.get_rect()
# set the rotated rectangle to the old center
rect.center = old_center
# drawing the rotated rectangle to the screen
screen.blit(new_image , rect)
# flipping the display after drawing everything
py.display.flip()
py.quit()
a more complex version of the quick replacement, in which you can define an arbitrary rotation center point for your rectangle - even outside of it (tested in python3):
def rectRotated( surface, color, pos, fill, border_radius, rotation_angle, rotation_offset_center = (0,0), nAntialiasingRatio = 1 ):
"""
- rotation_angle: in degree
- rotation_offset_center: moving the center of the rotation: (-100,0) will turn the rectangle around a point 100 above center of the rectangle,
if (0,0) the rotation is at the center of the rectangle
- nAntialiasingRatio: set 1 for no antialising, 2/4/8 for better aliasing
"""
nRenderRatio = nAntialiasingRatio
sw = pos[2]+abs(rotation_offset_center[0])*2
sh = pos[3]+abs(rotation_offset_center[1])*2
surfcenterx = sw//2
surfcentery = sh//2
s = pg.Surface( (sw*nRenderRatio,sh*nRenderRatio) )
s = s.convert_alpha()
s.fill((0,0,0,0))
rw2=pos[2]//2 # halfwidth of rectangle
rh2=pos[3]//2
pg.draw.rect( s, color, ((surfcenterx-rw2-rotation_offset_center[0])*nRenderRatio,(surfcentery-rh2-rotation_offset_center[1])*nRenderRatio,pos[2]*nRenderRatio,pos[3]*nRenderRatio), fill*nRenderRatio, border_radius=border_radius*nRenderRatio )
s = pygame.transform.rotate( s, rotation_angle )
if nRenderRatio != 1: s = pygame.transform.smoothscale(s,(s.get_width()//nRenderRatio,s.get_height()//nRenderRatio))
incfromrotw = (s.get_width()-sw)//2
incfromroth = (s.get_height()-sh)//2
surface.blit( s, (pos[0]-surfcenterx+rotation_offset_center[0]+rw2-incfromrotw,pos[1]-surfcentery+rotation_offset_center[1]+rh2-incfromroth) )
You cannot rotate a rectangle drawn by pygame.draw.rect. You have to create a transparent pygame.Surface and rotate the Surface:
rect_surf = pygame.Surface((widht, height), pygame.SRCLAPHA)
rect_surf.fill(color)
See How do I rotate an image around its center using PyGame?, to rotate the Surface.
I made a class which handles the rotation for you...
Extended from Ashish's design
from pygame import Surface, transform
from consts import screen
class BaseEntity:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
class Rectangle(BaseEntity):
def __init__(self, x: int, y: int, width: int, height: int, color: tuple):
super().__init__(x, y)
self.width = width
self.height = height
self.color = color
self.rotatation = 0
# the rectangle is a surface itself
self.surface = Surface((width, height))
self.surface.set_colorkey((0, 0, 0))
self.surface.fill(color)
self.rect = self.surface.get_rect()
def display(self, angle=None):
# updating values
self.surface.fill(
self.color
) # refill the surface color if you change it somewhere in the program
self.rect = self.surface.get_rect()
self.rect.center = (self.x, self.y)
# renderer
if angle is not None:
self.rotatation = angle
old_center = self.rect.center
new = transform.rotate(self.surface, self.rotatation)
self.rect = new.get_rect()
self.rect.center = old_center
screen.blit(new, self.rect)
Using a bit of trigonometry and the polygon function, I'm able to draw a rotated rectangle.
import math
import pygame.draw
def draw_rectangle(x, y, width, height, color, rotation=0):
"""Draw a rectangle, centered at x, y.
Arguments:
x (int/float):
The x coordinate of the center of the shape.
y (int/float):
The y coordinate of the center of the shape.
width (int/float):
The width of the rectangle.
height (int/float):
The height of the rectangle.
color (str):
Name of the fill color, in HTML format.
"""
points = []
# The distance from the center of the rectangle to
# one of the corners is the same for each corner.
radius = math.sqrt((height / 2)**2 + (width / 2)**2)
# Get the angle to one of the corners with respect
# to the x-axis.
angle = math.atan2(height / 2, width / 2)
# Transform that angle to reach each corner of the rectangle.
angles = [angle, -angle + math.pi, angle + math.pi, -angle]
# Convert rotation from degrees to radians.
rot_radians = (math.pi / 180) * rotation
# Calculate the coordinates of each point.
for angle in angles:
y_offset = -1 * radius * math.sin(angle + rot_radians)
x_offset = radius * math.cos(angle + rot_radians)
points.append((x + x_offset, y + y_offset))
pygame.draw.polygon(screen, color, points)
https://replit.com/#TimSwast1/RotateARectanlge?v=1
a quick replacement of the base pygame function adding rotation:
def rectRotated( surface, color, pos, fill, border_radius, angle ):
"""
- angle in degree
"""
max_area = max(pos[2],pos[3])
s = pg.Surface((max_area,max_area))
s = s.convert_alpha()
s.fill((0,0,0,0))
pg.draw.rect(s, color,(0,0,pos[2],pos[3]),fill, border_radius=border_radius)
s = pygame.transform.rotate(s,angle)
surface.blit( s, (pos[0],pos[1]) )
This code simulates rotating rectangles falling towards the ground. I used it in one of my games to make the background look awesome
import pygame
import random
class Square(pygame.sprite.Sprite):
def __init__(self, x, y):
super(Square, self).__init__()
self.win = win
self.color = (128, 128, 128)
self.speed = 3
self.angle = 0
self.side = random.randint(15, 40)
self.surface = pygame.Surface((self.side, self.side), pygame.SRCALPHA)
self.surface.set_colorkey((200,200,200))
self.rect = self.surface.get_rect(center=(x, y))
def update(self, win):
center = self.rect.center
self.angle = (self.angle + self.speed) % 360
image = pygame.transform.rotate(self.surface , self.angle)
self.rect = image.get_rect()
self.rect.center = center
self.rect.y += 1.5
if self.rect.top >= HEIGHT:
self.kill()
pygame.draw.rect(self.surface, self.color, (0,0, self.side, self.side), 4)
win.blit(image, self.rect)
if __name__ == '__main__':
pygame.init()
SCREEN = WIDTH, HEIGHT = 288, 512
win = pygame.display.set_mode(SCREEN, pygame.NOFRAME)
clock = pygame.time.Clock()
FPS = 60
count = 0
square_group = pygame.sprite.Group()
running = True
while running:
win.fill((200,200,200))
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
count += 1
if count % 100 == 0:
x = random.randint(40, WIDTH-40)
y = 0
square = Square(x, y)
square_group.add(square)
count = 0
square_group.update(win)
pygame.draw.rect(win, (30,30,30), (0, 0, WIDTH, HEIGHT), 8)
clock.tick(FPS)
pygame.display.update()
pygame.quit()
Here's the output, it's not an gif though
Now if you want color filled rectangle instead of bordered only, update this line on line 31
pygame.draw.rect(self.surface, self.color, (0,0, self.side, self.side))
and if you don't want the rectangle to fall down comment line 26
A concise and fast function to draw a rotated rectangle. Uses NumPy
def rectRotated(self, surface, rect, color, rotation):
"""
Draws a rotated Rect.
surface: pygame.Surface
rect: pygame.Rect
color: pygame.Color
rotation: float (degrees)
return: np.ndarray (vertices)
"""
# calculate the rotation in radians
rot_radians = -rotation * pi / 180
# calculate the points around the center of the rectangle, taking width and height into account
angle = atan2(rect.height / 2, rect.width / 2)
angles = [angle, -angle + pi, angle + pi, -angle]
radius = sqrt((rect.height / 2)**2 + (rect.width / 2)**2)
# create a numpy array of the points
points = np.array([
[rect.x + radius * cos(angle + rot_radians), rect.y + radius * sin(angle + rot_radians)]
for angle in angles
])
# draw the polygon
pygame.draw.polygon(surface, color, points)
# return the vertices of the rectangle
return points

Categories