Assignment :
Open a .obj file which contains v(vertices) and f(faces) and render it using OpenGL!
Sample can be found at : https://groups.csail.mit.edu/…/models/
In this assignment I am using python as my language. Unfortunately, OpenGL module is not included by default so we need to install it first using pip. I also include pygame to help me creating the window so we need to install that too.
pip install PyOpenGL PyOpenGL_accelerate
pip install pygame
If you are using Windows as your OS make sure that PyOpenGL installed properly as there are cases where the installation doesn’t behave correctly.
My assignment is completed by heavily modifying existing script created by a github user edward344 to comply with what I need. However, in this writeup I will try writing the code from scratch to make it easier to understand.
To see how we are going to approach this assignment we need to check what is .obj file itself.
From wikipedia, we can say that .obj is a file contains : geometric vertices(v), texture coordinates(vt), vertex normals(vn), parameter space vertices(vp), and face element(f). Those elements are used to represent a 3D geometry.
In samples given, we will use teddy.obj as our obj file
v 7.49304 -16.145 -3.85419
v 3.89555 -16.6003 -5.65267
v 7.1955 -17.2256 -2.10135
v 5.50509 -16.7471 -4.66901
v 6.13653 -15.9249 -5.25062
v 6.29974 -17.1925 -3.43905
v 6.44628 -14.1865 -6.82055
v 5.00347 -16.345 -5.4749
v 6.98854 -16.9314 -3.18964
f 3 4 2
f 8 9 7
f 8 7 6
f 10 9 8
f 14 13 4
f 7 16 15
f 9 17 16
f 9 16 7
f 21 22 20
f 21 20 19
Looking at the file, we found v element and f element used to create a triangle mesh so we need to work with those 2 variables.
In python, OpenGL function to draw triangle uses tuple as its vertex and faces parameter so we want to parse our variables into tuples too.
Create a module to parse the file and save it as an object to render in the main window, name it as grafkom1Framework.py.
# grafkom1Framework.py
class ObjLoader(object):
def __init__(self, fileName):
self.vertices = []
self.faces = []
##
try:
f = open(fileName)
for line in f:
if line[:2] == "v ":
index1 = line.find(" ") + 1
index2 = line.find(" ", index1 + 1)
index3 = line.find(" ", index2 + 1)
vertex = (float(line[index1:index2]), float(line[index2:index3]), float(line[index3:-1]))
vertex = (round(vertex[0], 2), round(vertex[1], 2), round(vertex[2], 2))
self.vertices.append(vertex)
elif line[0] == "f":
string = line.replace("//", "/")
##
i = string.find(" ") + 1
face = []
for item in range(string.count(" ")):
if string.find(" ", i) == -1:
face.append(string[i:-1])
break
face.append(string[i:string.find(" ", i)])
i = string.find(" ", i) + 1
##
self.faces.append(tuple(face))
f.close()
except IOError:
print(".obj file not found.")
Since code above is a modified code, we see some unnecessary stuff added to handle different format of .obj which includes elements of vt, vn, and variations of value in f. For now, we can ignore those and focus on our parsed data which now are stored as tuple and can be used to render triangles in OpenGL
vertex = {tuple}: (a, b, c)
faces = {tuple}: (x, y, z)
Since we have vertex and faces now, we can make a function in the class to render our variables into triangles. To do this however, now we need to import OpenGL module.
# grafkom1Framework.py
from OpenGL.GL import * #move this to line 1
def render_scene(self):
if len(self.faces) > 0:
##
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
glBegin(GL_TRIANGLES)
for face in self.faces:
for f in face:
vertexDraw = self.vertices[int(f) - 1]
if int(f) % 3 == 1:
glColor4f(0.282, 0.239, 0.545, 0.35)
elif int(f) % 3 == 2:
glColor4f(0.729, 0.333, 0.827, 0.35)
else:
glColor4f(0.545, 0.000, 0.545, 0.35)
glVertex3fv(vertexDraw)
glEnd()
##
In creating a main script we must import modules needed to create GUI and using OpenGL function, we also want to use math module to coordinate our camera system, as well as our grafkom1Framework.
# grafkom1.py
import pygame
from OpenGL.GL import *
from OpenGL.GLU import *
import math
import grafkom1Framework as graphics
Since we only have 1 object to display, we can fit our screen and object in one class.
For class init, we will input our camera angle, coordinates of our screen, background color, camera position, and lastly our obj.
# grafkom1.py
class objItem(object):
def __init__(self):
self.angle = 0
self.vertices = []
self.faces = []
self.coordinates = [0, 0, -65] # [x,y,z]
self.teddy = graphics.ObjLoader("teddy.obj")
self.position = [0, 0, -50]
and our function to render the scene.
# grafkom1.py
def render_scene(self):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glClearColor(0.902, 0.902, 1, 0.0)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
gluLookAt(0, 0, 0, math.sin(math.radians(self.angle)), 0, math.cos(math.radians(self.angle)) * -1, 0, 1, 0)
glTranslatef(self.coordinates[0], self.coordinates[1], self.coordinates[2])
In pygame, we want to move around in the space using user input. I also added one extra function to rotate the camera while focusing the object in front of camera.
# grafkom1.py
def move_forward(self):
self.coordinates[2] += 10 * math.cos(math.radians(self.angle))
self.coordinates[0] -= 10 * math.sin(math.radians(self.angle))
def move_back(self):
self.coordinates[2] -= 10 * math.cos(math.radians(self.angle))
self.coordinates[0] += 10 * math.sin(math.radians(self.angle))
def move_left(self, n):
self.coordinates[0] += n * math.cos(math.radians(self.angle))
self.coordinates[2] += n * math.sin(math.radians(self.angle))
def move_right(self, n):
self.coordinates[0] -= n * math.cos(math.radians(self.angle))
self.coordinates[2] -= n * math.sin(math.radians(self.angle))
def rotate(self, n):
self.angle += n
def fullRotate(self):
for i in range(0, 36):
self.angle += 10
self.move_left(10)
self.render_scene()
self.teddy.render_scene()
pygame.display.flip()
##
Moving on to main class, initialize our pygame window, check the features of gl to use only what we need, and set our matrix.
# grafkom1.py
def main():
pygame.init()
pygame.display.set_mode((640, 480), pygame.DOUBLEBUF | pygame.OPENGL)
pygame.display.set_caption("Teddy - Tugas Grafkom 1")
clock = pygame.time.Clock()
# Feature checker
glDisable(GL_TEXTURE_2D)
glEnable(GL_DEPTH_TEST)
glEnable(GL_BLEND)
glEnable(GL_CULL_FACE)
#
glMatrixMode(GL_PROJECTION)
gluPerspective(45.0, float(800) / 600, .1, 1000.)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
Declare our object
# grafkom1.py
objectTeddy = objItem()
Finally create an event loop to render and display our scene and object to the window as well as handling user events.
# grafkom1.py
# - Event Loop - #
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT or event.key == pygame.K_a:
objectTeddy.move_left(10)
elif event.key == pygame.K_RIGHT or event.key == pygame.K_d:
objectTeddy.move_right(10)
elif event.key == pygame.K_UP or event.key == pygame.K_w:
objectTeddy.move_forward()
elif event.key == pygame.K_DOWN or event.key == pygame.K_s:
objectTeddy.move_back()
elif event.key == pygame.K_1:
objectTeddy.rotate(10)
objectTeddy.move_left(10)
elif event.key == pygame.K_2:
objectTeddy.rotate(-10)
objectTeddy.move_right(10)
elif event.key == pygame.K_3:
objectTeddy.fullRotate()
objectTeddy.render_scene()
objectTeddy.teddy.render_scene()
pygame.display.flip()
clock.tick(30)
pygame.quit()
And run main.
# grafkom1.py
if __name__ == '__main__':
main()
Thats it!
If you want, you can download the source code for this script in my github repository here and try making one for youself or even converting it to another programming languages.
Examples of how the script runs :