Search code examples
pythonopenglpygamepyopenglopengl-compat

Cannot draw to the same position using gluSphere()?


I am making a game using PyOpenGL. I want to have bullets shot from a place every 4 seconds, but when I used gluSphere() to draw the bullet it doesn't appear to the place I excepted (except the first time of drawing the sphere). How can I solve it?

Code that is relevant:

import threading
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

class Object:
    def __init__(self):
        self.bullet = []

def draw_bullet(pos):
    glTranslated(pos[0], pos[1], pos[2]) #Move to the place
    glColor4f(1, 1, 1, 1) #Put color
    gluSphere(sphere, 0.5, 32, 16) #Draw sphere

def shoot(obj):
    global shoottimer
    shoottimer = None
    obj.bullet.append(pos)

object = Object()
shoottimer = None
pos = [0,0,0]
while True:
    if shoottimer == None:
        shoottimer = threading.Timer(4,shoot,args = [object])
        shoottimer.start()
    for bullet in person.bullet:
        person.draw_bullet(bullet)

Full code:

#!/usr/local/bin/python3
import pygame
from pygame.locals import *

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

import math,sys,numpy,random,ctypes,threading,os

pygame.init()
display = (1440, 900)
screen = pygame.display.set_mode(display, DOUBLEBUF | OPENGL | RESIZABLE)

glEnable(GL_DEPTH_TEST)
glEnable(GL_LIGHTING)
glShadeModel(GL_SMOOTH)
glEnable(GL_COLOR_MATERIAL)
glEnable(GL_BLEND)
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)

glEnable(GL_LIGHT0)
glLightfv(GL_LIGHT0, GL_AMBIENT, [0.5, 0.5, 0.5, 1])
glLightfv(GL_LIGHT0, GL_DIFFUSE, [1.0, 1.0, 1.0, 1])

glMatrixMode(GL_PROJECTION)
gluPerspective(45, (display[0]/display[1]), 0.1, 50.0)

glMatrixMode(GL_MODELVIEW)
gluLookAt(0, -8, 0, 0, 0, 0, 0, 0, 1)
glTranslatef(0,-8,0)
viewMatrix = glGetFloatv(GL_MODELVIEW_MATRIX)
glLoadIdentity()

# init mouse movement and center mouse on screen
displayCenter = [screen.get_size()[i] // 2 for i in range(2)]
mouseMove = [0, 0]
pygame.mouse.set_pos(displayCenter)

cmddown = False
light = 0
mouseclick = False
cam_attack = False
cam_damage = random.randint(20,30)
max_health = 100
health = max_health
knockback = False
knocktimer = None
shoottimer = None
sphere = gluNewQuadric()
dist = None
dist_list = []
person_count = 1
up_down_angle = 0.0
camera_pos = (0,0,0)
paused = False
run = True
#xzy = xyz

#Functions & Classes
def InverseMat44(mat):
    m = [mat[i][j] for i in range(4) for j in range(4)]
    inv = [0]*16

    inv[0]  =  m[5] * m[10] * m[15] - m[5] * m[11] * m[14] - m[9] * m[6] * m[15] + m[9] * m[7] * m[14] + m[13] * m[6] * m[11] - m[13] * m[7] * m[10]
    inv[4]  = -m[4] * m[10] * m[15] + m[4] * m[11] * m[14] + m[8] * m[6] * m[15] - m[8] * m[7] * m[14] - m[12] * m[6] * m[11] + m[12] * m[7] * m[10]
    inv[8]  =  m[4] * m[9]  * m[15] - m[4] * m[11] * m[13] - m[8] * m[5] * m[15] + m[8] * m[7] * m[13] + m[12] * m[5] * m[11] - m[12] * m[7] * m[9]
    inv[12] = -m[4] * m[9]  * m[14] + m[4] * m[10] * m[13] + m[8] * m[5] * m[14] - m[8] * m[6] * m[13] - m[12] * m[5] * m[10] + m[12] * m[6] * m[9]
    inv[1]  = -m[1] * m[10] * m[15] + m[1] * m[11] * m[14] + m[9] * m[2] * m[15] - m[9] * m[3] * m[14] - m[13] * m[2] * m[11] + m[13] * m[3] * m[10]
    inv[5]  =  m[0] * m[10] * m[15] - m[0] * m[11] * m[14] - m[8] * m[2] * m[15] + m[8] * m[3] * m[14] + m[12] * m[2] * m[11] - m[12] * m[3] * m[10]
    inv[9]  = -m[0] * m[9]  * m[15] + m[0] * m[11] * m[13] + m[8] * m[1] * m[15] - m[8] * m[3] * m[13] - m[12] * m[1] * m[11] + m[12] * m[3] * m[9]
    inv[13] =  m[0] * m[9]  * m[14] - m[0] * m[10] * m[13] - m[8] * m[1] * m[14] + m[8] * m[2] * m[13] + m[12] * m[1] * m[10] - m[12] * m[2] * m[9]
    inv[2]  =  m[1] * m[6]  * m[15] - m[1] * m[7]  * m[14] - m[5] * m[2] * m[15] + m[5] * m[3] * m[14] + m[13] * m[2] * m[7]  - m[13] * m[3] * m[6]
    inv[6]  = -m[0] * m[6]  * m[15] + m[0] * m[7]  * m[14] + m[4] * m[2] * m[15] - m[4] * m[3] * m[14] - m[12] * m[2] * m[7]  + m[12] * m[3] * m[6]
    inv[10] =  m[0] * m[5]  * m[15] - m[0] * m[7]  * m[13] - m[4] * m[1] * m[15] + m[4] * m[3] * m[13] + m[12] * m[1] * m[7]  - m[12] * m[3] * m[5]
    inv[14] = -m[0] * m[5]  * m[14] + m[0] * m[6]  * m[13] + m[4] * m[1] * m[14] - m[4] * m[2] * m[13] - m[12] * m[1] * m[6]  + m[12] * m[2] * m[5]
    inv[3]  = -m[1] * m[6]  * m[11] + m[1] * m[7]  * m[10] + m[5] * m[2] * m[11] - m[5] * m[3] * m[10] - m[9]  * m[2] * m[7]  + m[9]  * m[3] * m[6]
    inv[7]  =  m[0] * m[6]  * m[11] - m[0] * m[7]  * m[10] - m[4] * m[2] * m[11] + m[4] * m[3] * m[10] + m[8]  * m[2] * m[7]  - m[8]  * m[3] * m[6]
    inv[11] = -m[0] * m[5]  * m[11] + m[0] * m[7]  * m[9]  + m[4] * m[1] * m[11] - m[4] * m[3] * m[9]  - m[8]  * m[1] * m[7]  + m[8]  * m[3] * m[5]
    inv[15] =  m[0] * m[5]  * m[10] - m[0] * m[6]  * m[9]  - m[4] * m[1] * m[10] + m[4] * m[2] * m[9]  + m[8]  * m[1] * m[6]  - m[8]  * m[2] * m[5]

    det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12]
    for i in range(16):
        inv[i] /= det
    return inv

def clicked(tar_x,tar_y,tarimg):
    if tar_x < cursor_x < tar_x + tarimg.get_width() and tar_y < cursor_y < tar_y + tarimg.get_height():
        return True
    return False

def follower(x,y,z,x1,y1,z1,speed):
    dir_x, dir_y = (x1-x, y1-y)
    distance = math.hypot(dir_x, dir_y)
    dir_x, dir_y = (dir_x/distance, dir_y/distance)
    angle = math.degrees(math.atan2(dir_y, dir_x)) + 90
    return (dir_x*speed, dir_y*speed, 0, angle)

def random_pos(max_distance):
    x_value_change = random.randrange(-max_distance + 2,max_distance + 2)
    y_value_change = random.randrange(-max_distance + 20,max_distance + 3)
    z_value_change = 0
    return (x_value_change, y_value_change, z_value_change)

def blit_text(x,y,font,text,r,g,b):
    glColor3f(r,g,b)
    glWindowPos2f(x,y)
    for ch in text:
        glutBitmapCharacter(font,ctypes.c_int(ord(ch)))

def stopknock() :
    global knockback,knocktimer,health
    knockback = False
    knocktimer = None
    health -= random.randint(20,40)

def shoot(obj):
    global shoottimer
    shoottimer = None
    obj.bullet.append(obj.pos)

def subtract(v0, v1):
    return [v0[0]-v1[0], v0[1]-v1[1], v0[2]-v1[2]]
def dot(v0, v1):
    return v0[0]*v1[0]+v0[1]*v1[1]+v0[2]*v1[2]
def length(v):
    return math.sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2])
def mults(v, s):
    return [v[0]*s, v[1]*s, v[2]*s]
def add(v0, v1):
    return [v0[0]+v1[0], v0[1]+v1[1], v0[2]+v1[2]]
def cross(v0, v1):
    return [
        v0[1]*v1[2]-v1[1]*v0[2],
        v0[2]*v1[0]-v1[2]*v0[0],
        v0[0]*v1[1]-v1[0]*v0[1]]
def normalize(v):
    l = length(v)
    return [v[0]/l, v[1]/l, v[2]/l]
def PointInOrOn( P1, P2, A, B ):
    CP1 = cross(subtract(B, A), subtract(P1, A))
    CP2 = cross(subtract(B, A), subtract(P2, A))
    return dot( CP1, CP2 ) >= 0
def PointInOrOnTriangle( P, A, B, C ):
    return PointInOrOn(P, A, B, C) and PointInOrOn(P, B, C, A) and PointInOrOn(P, C, A, B)

def isectPlane(p0, p1, PA, PB, PC):
    R0 = p0      # origin 
    D = normalize(subtract(p1, p0))
    P0 = PA
    NV = normalize(cross(subtract(PB, PA), subtract(PC, PA)))
    dist_isect = dot(subtract(P0, R0), NV) / dot(D, NV) 
    P_isect = add(R0, mults(D, dist_isect))
    return P_isect, dist_isect

def isectQuad(p0, p1, PA, PB, PC, PD):
    P, t = isectPlane(p0, p1, PA, PB, PC)
    if t >= 0 and (PointInOrOnTriangle(P, PA, PB, 
        PC) or PointInOrOnTriangle(P, PA, PC, PD)):
        return t
    return None

def isectCuboid(p0, p1, pMin, pMax):
    t = None
    try:
        pl = [ [pMin[0], pMin[1], pMin[2]], [pMax[0], pMin[1], pMin[2]], [pMax[0], pMax[1], pMin[2]], [pMin[0], pMax[1], pMin[2]],
               [pMin[0], pMin[1], pMax[2]], [pMax[0], pMin[1], pMax[2]], [pMax[0], pMax[1], pMax[2]], [pMin[0], pMax[1], pMax[2]] ]
        il = [[0, 1, 2, 3], [4, 5, 6, 7], [4, 0, 3, 7], [1, 5, 6, 2], [4, 3, 1, 0], [3, 2, 6, 7]]
        for qi in il:
            ts = isectQuad(p0, p1, pl[qi[0]], pl[qi[1]], pl[qi[2]], pl[qi[3]])
            if ts != None and ts >= 0 and (t == None or ts < t):
                t = ts
    except:
        t = None
    return t

class Ground:
    def __init__(self,mul=1):
        self.vertices = [
        [-20,20,-1],
        [20,20,-1],
        [-20,-300,-1],
        [20,-300,-1]
        ]

    def draw(self):
        glBegin(GL_QUADS) #Begin fill
        for vertex in self.vertices:
            glColor3f(0,0.5,0.5)
            glVertex3fv(vertex)
        glEnd()

class Person:
    def __init__(self):
        self.vertices = [
            [-1,0,1],
            [-1,0,-1],
            [1,0,-1],
            [1,0,1],
            [-1,1,1],
            [-1,1,-1],
            [1,1,-1],
            [1,1,1]
        ]

        self.vertices = list(numpy.multiply(numpy.array(self.vertices),1))
        self.edges = (
            (0,1),
            (0,3),
            (0,4),
            (1,2),
            (1,5),
            (2,3),
            (2,6),
            (3,7),
            (4,5),
            (4,7),
            (5,6),
            (6,7)
            )
        self.surfaces = (
            (0,1,2,3),
            (0,1,5,4),
            (4,5,6,7),
            (1,2,6,5),
            (0,3,7,4),
            (2,3,7,6)
            )
        self.x = self.vertices[1][0]
        self.y = self.vertices[1][2]
        self.z = self.vertices[1][1]
        self.pos = (self.x,self.y,self.z)
        self.rot = 0
        self.health = 100
        self.damage = random.randint(20,40)
        self.bullet = []

    def draw(self):
        glTranslated(self.pos[0], self.pos[1], self.pos[2])
        glRotated(self.rot,0,0,1)

        #Get current view matrix, projection matrix and viewport rectangle 
        mv_matrix = glGetDoublev(GL_MODELVIEW_MATRIX)
        proj_matrix = glGetDoublev(GL_PROJECTION_MATRIX)
        vp_rect = glGetIntegerv(GL_VIEWPORT)

        #Calculate "near" and "far" point 
        pt_near = gluUnProject(displayCenter[0], displayCenter[1], 0, mv_matrix, proj_matrix, vp_rect)
        pt_far  = gluUnProject(displayCenter[0], displayCenter[1], 1, mv_matrix, proj_matrix, vp_rect)
        dist = isectCuboid(pt_near, pt_far, [-1, 0, -1], [1, 1, 1])

        glBegin(GL_QUADS) #Begin fill
        for surface in self.surfaces:
            for vertex in surface:
                glColor3f(0,1,0)
                glVertex3fv(self.vertices[vertex])
        glEnd()
        glLineWidth(5) #Set width of the line
        glBegin(GL_LINES) #Begin outline
        for edge in self.edges:
            for vertex in edge:
                glColor3f(1,1,0)
                glVertex3fv(self.vertices[vertex])
        glEnd()
        return dist

    def draw_bullet(self,pos): #This is the part where I got stuck
        glLoadIdentity()
        glTranslated(pos[0], pos[1], pos[2]) #Move to the place
        glColor4f(1, 1, 1, 1) #Put color
        gluSphere(sphere, 0.5, 32, 16) #Draw sphere

    def move(self,x,y,z):
        self.pos = (self.pos[0]+x,self.pos[1]+y,self.pos[2]+z)

glutInit()
persons = [Person() for person in range(person_count)]
ground = Ground()
for person in persons:
    person.pos = random_pos(12)
while run:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LMETA:
                cmddown = True
            if event.key == pygame.K_ESCAPE:
                paused = not paused
            if event.key == pygame.K_q:
                if cmddown:
                    run = False
        if not paused:
            if event.type == pygame.MOUSEMOTION:
                mouseMove = [event.pos[i] - displayCenter[i] for i in range(2)]
                pygame.mouse.set_pos(displayCenter)
            if event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 1:
                    cam_attack = True
        else:
            if event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 1:
                    mouseclick = True
        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LMETA:
                cmddown = False

    pygame.mouse.set_visible(False)
    if not paused:
        pygame.mouse.set_pos(displayCenter)
        #Get keys
        keypress = pygame.key.get_pressed()

        #Init model view matrix
        glLoadIdentity()

        #------------------------View------------------------
        #Apply the look up and down (with 90° angle limit)
        if up_down_angle < -90:
            if mouseMove[1] > 0:
                up_down_angle += mouseMove[1]*0.1
        elif up_down_angle > 90:
            if mouseMove[1] < 0:
                up_down_angle += mouseMove[1]*0.1
        else:
            up_down_angle += mouseMove[1]*0.1
        glRotatef(up_down_angle, 1.0, 0.0, 0.0)

        #Init the view matrix
        glPushMatrix()
        glLoadIdentity()

        #Apply the movement 
        if keypress[pygame.K_w]:
            glTranslatef(0,0,0.1)
        if keypress[pygame.K_s]:
            glTranslatef(0,0,-0.1)
        if keypress[pygame.K_d]:
            glTranslatef(-0.1,0,0)
        if keypress[pygame.K_a]:
            glTranslatef(0.1,0,0)

        #Apply the look left and right
        glRotatef(mouseMove[0]*0.1, 0.0, 1.0, 0.0)
        #------------------------View------------------------

        #Multiply the current matrix by the new view matrix and store the final view matrix 
        glMultMatrixf(viewMatrix)
        #Knockback
        if knockback:
            if knocktimer == None:
                knockback_dist = 0.4
                knockback_dir = normalize(subtract(knockback, camera_pos))
            glTranslatef(knockback_dir[0]*knockback_dist, knockback_dir[1]*knockback_dist, 0)
            if knocktimer == None:
                knocktimer = threading.Timer(0.2,stopknock)
                knocktimer.start()
        #Bullet
        for person in persons:
            if shoottimer == None:
                shoottimer = threading.Timer(4,shoot,args = [person])
                shoottimer.start()
        viewMatrix = glGetFloatv(GL_MODELVIEW_MATRIX)
        invVM = InverseMat44(viewMatrix)
        camera_pos = (invVM[12],invVM[13],invVM[14])

        #Apply view matrix
        glPopMatrix()
        glMultMatrixf(viewMatrix)

        glLightfv(GL_LIGHT0, GL_POSITION, [1, -1, 1, 0])

        #Follow, attack
        crosshair_color = (1,1,1)
        attacklist = []
        for counter,person in enumerate(persons):
            freturn = follower(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2],0.02)
            xchange,ychange,zchange = freturn[0],freturn[1],freturn[2]
            person.rot = freturn[3]
            if (touched(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2])) < 3:
                xchange,ychange,zchange = 0,0,0
                knockback = person.pos
            person.move(xchange,ychange,zchange)
            if (touched(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2])) < 5 and distlist[counter] != None:
                crosshair_color = (1,0,0)
                if len(attacklist) >= 2:
                    bigger = attacklist[1] > touched(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2])
                    if bigger == True:
                        attacklist = [person,touched(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2])]
                else:
                    attacklist = [person,touched(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2])]
        distlist = []
        if attacklist:
            if cam_attack == True:
                attacklist[0].health -= cam_damage

        glClearColor(0.53 + light, 0.63 + light, 0.98 + light, 1) #Change background colour (sky blue)
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

        blit_text(displayCenter[0] - 700,displayCenter[1] + 410,GLUT_BITMAP_TIMES_ROMAN_24,"Health: " + str(health) + "/" + str(max_health),0,0,0)

        #Draw crosshair, health 
        if health > 0:
            blit_text(displayCenter[0] - 5,displayCenter[1] - 5,GLUT_BITMAP_TIMES_ROMAN_24,"+",crosshair_color[0],crosshair_color[1],crosshair_color[2])

        glPushMatrix()

        glColor4f(0.2, 0.2, 0.5, 1)
        for person in persons:
            glPushMatrix()
            distlist.append(person.draw())
            for bullet in person.bullet:
                person.draw_bullet(bullet)
            glPopMatrix()

        ground.draw()
        glPopMatrix()

        for person in persons:
            if person.health <= 0:
                persons.remove(person)

        if not persons and health > 0:
            pass #Show victory

        if health <= 0:
            light -= 0.04
            glLightfv(GL_LIGHT0, GL_AMBIENT, [0.5 + light, 0.5 + light, 0.5 + light, 1])
            glLightfv(GL_LIGHT0, GL_DIFFUSE, [1.0 + light, 1.0 + light, 1.0 + light, 1])

        cam_attack = False

    else:
        cursor_x,cursor_y = pygame.mouse.get_pos()
        pygame.mouse.set_visible(True)
        glClearColor(0.53 + light, 0.63 + light, 0.98 + light, 1) #Change background colour (sky blue)
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
        mouseclick = False
        for person in persons:
            glPushMatrix()
            distlist.append(person.draw())
            for bullet_shot in person.bullet:
                person.draw_bullet(bullet_shot)
            glPopMatrix()
        ground.draw()
    pygame.display.flip()
    pygame.time.wait(10)
pygame.quit()

I expected it to draw the spheres, but instead it turned off the lightings and did nothing (maybe drawn somewhere else).


Solution

  • Each object has its model matrix. The model matrix is applied by glTranslate respctively glRotate.
    Calling this functions change the current matrix. To ensure that the model transformation is only applied to 1 model and not to all the models which are drawn after, the current matrix has to be "saved" by glPushMatrix before changing the current matrix and has to be "restored" by glPopMatrix after drawing the model.

    Do this when you draw the Person object:

    class Person:
    
        # [...]
    
        def draw(self):
    
            glPushMatrix() # <--- save
    
            glTranslated(self.pos[0], self.pos[1], self.pos[2])
            glRotated(self.rot,0,0,1)
    
            # [...]
    
            glPopMatrix()  # <--- restore
            return dist
    

    And when you draw the bullet:

    class Person:
    
        # [...]
    
        def draw_bullet(self,pos): #This is the part where I got stuck
    
            glPushMatrix() # <--- save
    
            glTranslated(pos[0], pos[1], pos[2]) #Move to the place
            glColor4f(1, 1, 1, 1) #Put color
            gluSphere(sphere, 0.5, 32, 16) #Draw sphere
    
            glPopMatrix()  # <--- restore
    

    Note, the view matrix is set before the model matrix. The matrix which define by glTranslate is multiplied to the current matrix. That is what you want:

    mdelview = view * model
    

    So you've to skip setting the identity matrix before setting the model:

    def draw_bullet(self,pos): #This is the part where I got stuck
    
        glPushMatrix()
    
        # glLoadIdentity() <--- delete
    
        glTranslated(pos[0], pos[1], pos[2]) #Move to the place
        glColor4f(1, 1, 1, 1) #Put color
        gluSphere(sphere, 0.5, 32, 16) #Draw sphere
        glPopMatrix()
    

    Further I recommend to copy the position when a new bullet spawns:

    def shoot(obj):
        global shoottimer
        shoottimer = None
    
        # obj.bullet.append(obj.pos)
        obj.bullet.append(obj.pos[:])