Search code examples
pythonpyopengl

pyopengl - creating a cylinder without using gluCylinder function


Using pyopengl, I am trying to create a cylinder. The top of the cylinder is not circular whilst the bottom is as shown in the image linked below. I would like to know how to fix this, if its the way I have coded it or simply the way I have done it does not work with pyopengl. I am using Pygame 1.9.2, Python 3.5 and PyOpenGL-3.1.0.

https://i.sstatic.net/KYPLY.jpg

import pygame
from pygame.locals import *

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





def securityCamera(radius,halflength,slices):
    glBegin(GL_TRIANGLES)
    for i in range(1,slices+1):
        angleSize=(2*math.pi)/slices
        theta=i*angleSize
        nextTheta=(i+1)*angleSize

        glColor3fv((0/256,0/256,0/256))

        glVertex3f(radius*math.cos(nextTheta), halflength, radius*math.cos(nextTheta))
        glVertex3f(radius*math.cos(theta), halflength, radius*math.sin(theta))
        glVertex3f(0.0, halflength, 0.0)

        glVertex3f(radius*math.cos(nextTheta), -halflength, radius*math.sin(nextTheta))
        glVertex3f(radius*math.cos(theta), -halflength, radius*math.sin(theta))
        glVertex3f(0.0, -halflength, 0.0)

    glEnd()

    glBegin(GL_QUADS)
    for i in range(1,slices+1):
        angleSize=(2*math.pi)/slices
        theta=i*angleSize
        nextTheta=(i+1)*angleSize


        glColor3fv((256/256,256/256,256/256))
        glVertex3f(radius*math.cos(theta), halflength, radius*math.sin(theta))
        glVertex3f(radius*math.cos(nextTheta), halflength, radius*math.cos(nextTheta))
        glVertex3f(radius*math.cos(theta), -halflength, radius*math.sin(theta))
        glVertex3f(radius*math.cos(nextTheta), -halflength, radius*math.sin(nextTheta))
    glEnd()

Solution

  • A cylinder can be divided up into two circles and a rectangular tube/sheet that connects the two. To avoid duplicating some vertices, I used GL_TRIANGLE_FAN (for the circles) and GL_TRIANGLE_STRIP (for the tube) instead of GL_TRIANGLE and GL_QUAD. If you want to learn more about those, I suggest you look at this answer.

    Below is a demo that displays a rotating cylinder with a red end, a blue end, and a green tube in the middle (important code is in the draw_cylinder function):

    import pygame
    from pygame.locals import *
    from OpenGL.GL import *
    from OpenGL.GLU import *
    import math
    import sys
    
    def draw_cylinder(radius, height, num_slices):
        r = radius
        h = height
        n = float(num_slices)
    
        circle_pts = []
        for i in range(int(n) + 1):
            angle = 2 * math.pi * (i/n)
            x = r * math.cos(angle)
            y = r * math.sin(angle)
            pt = (x, y)
            circle_pts.append(pt)
    
        glBegin(GL_TRIANGLE_FAN)#drawing the back circle
        glColor(1, 0, 0)
        glVertex(0, 0, h/2.0)
        for (x, y) in circle_pts:
            z = h/2.0
            glVertex(x, y, z)
        glEnd()
    
        glBegin(GL_TRIANGLE_FAN)#drawing the front circle
        glColor(0, 0, 1)
        glVertex(0, 0, h/2.0)
        for (x, y) in circle_pts:
            z = -h/2.0
            glVertex(x, y, z)
        glEnd()
    
        glBegin(GL_TRIANGLE_STRIP)#draw the tube
        glColor(0, 1, 0)
        for (x, y) in circle_pts:
            z = h/2.0
            glVertex(x, y, z)
            glVertex(x, y, -z)
        glEnd()
    
    pygame.init()
    (width, height) = (800, 600)
    screen = pygame.display.set_mode((width, height), OPENGL | DOUBLEBUF)
    clock = pygame.time.Clock()
    rotation = 0.0
    
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
    
        rotation += 1.0
        glClear(GL_COLOR_BUFFER_BIT)
        glClear(GL_DEPTH_BUFFER_BIT)
        glEnable(GL_DEPTH_TEST)
    
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluPerspective(30, float(width)/height, 1, 1000)
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        glTranslate(0, 0, -50)#move back far enough to see this object 
        glRotate(rotation, 0, 1, 0)#NOTE: this is applied BEFORE the translation due to OpenGL multiply order
    
        draw_cylinder(5, 10, 20)
        pygame.display.flip()
        clock.tick(60)
    

    By the way, you are probably aware that fixed-function methods like glBegin and glVertex are really ancient and out-of-date, but I thought it would not hurt to mention. With this kind of code, you are basically sending vertices one at a time to the GPU, which is not very efficient. You might want to take a look at VBOs if you run into performance issues later down the road. Hope this helps!