class Scene(object): """ A class encapsulating the entire scene. Contains methods to initialize the OpenGL context, setup the camera and lighting, setup the shaders and the components of the scene. Displays the scene and handles user input. """ light = None camera = None ground_shader_program = None stone_shader_program = None objects = [] def __init__(self): """ Constructor. Executes the entire 3D pipeline. """ self.init_glut() self.light = Light() self.camera = Camera() self.setup_shaders() ground = Ground(self.ground_shader_program) ground.scale(GROUND_SIZE, 1.0, GROUND_SIZE) self.objects.append(ground) self.setup_stones() self.setup_boulders() glutDisplayFunc(self.display) glutKeyboardFunc(self.handle_key) glutMainLoop() @staticmethod def init_glut(): """ Sets up the OpenGL context using GLUT. """ glutInit() glutInitDisplayMode(GLUT_RGBA | GLUT_ALPHA | GLUT_DOUBLE | GLUT_DEPTH | GLUT_3_2_CORE_PROFILE) glutInitWindowSize(768, 768) glutCreateWindow(b"Final Project - Stonehenge") glEnable(GL_DEPTH_TEST) glClearColor(0.0, 0.2, 0.2, 1.0) glEnable(GL_CULL_FACE) glCullFace(GL_BACK) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) glClearDepth(1.0) def setup_shaders(self): """ Compiles vertex and fragment shaders and links them to shader programs used in the scene. Two sets of shaders are used - one for the ground and one for the stones. """ vertex_shader = self.compile_shader(GL_VERTEX_SHADER, GROUND_VERTEX_SHADER) fragment_shader = self.compile_shader(GL_FRAGMENT_SHADER, GROUND_FRAGMENT_SHADER) self.ground_shader_program = self.linkProgram(vertex_shader, fragment_shader) vertex_shader = self.compile_shader(GL_VERTEX_SHADER, STONE_VERTEX_SHADER) fragment_shader = self.compile_shader(GL_FRAGMENT_SHADER, STONE_FRAGMENT_SHADER) self.stone_shader_program = self.linkProgram(vertex_shader, fragment_shader) glDeleteShader(vertex_shader) glDeleteShader(fragment_shader) @staticmethod def compile_shader(shader_type, source_path): """ Compiles a shader from a given source file and returns a unique ID representing it. :param shader_type: The type of shader :param source_path: The path of the shader source file :return: A unique ID representing the compiled shader """ with open(source_path) as shader_file: source = shader_file.read() shader = glCreateShader(shader_type) glShaderSource(shader, source) glCompileShader(shader) log = glGetShaderInfoLog(shader) if log: print(log) return shader @staticmethod def linkProgram(vertex_shader, fragment_shader): """ Creates and returns a shader program after linking a vertex and fragment shader to it. :param vertex_shader: A unique ID representing the vertex shader :param fragment_shader: A unique ID representing the fragment shader :return: A unique ID representing the shader program """ shader_program = glCreateProgram() glAttachShader(shader_program, vertex_shader) glAttachShader(shader_program, fragment_shader) glLinkProgram(shader_program) log = glGetProgramInfoLog(shader_program) if log: print(log) return shader_program def setup_stones(self): """ Creates objects for the gray stones that make up Stonehenge and applies model transforms to them. """ stone1 = Stone(self.stone_shader_program) stone1.scale(1.5, 3.0, 0.7) stone1.rotate(0.0, 20.0, 0.0) stone1.translate(3.5, 3.0, 8.5) self.objects.append(stone1) stone2 = Stone(self.stone_shader_program) stone2.scale(1.5, 3.0, 0.7) stone2.rotate(0.0, 45.0, 0.0) stone2.translate(7.8, 3.0, 5.8) self.objects.append(stone2) stone3 = Stone(self.stone_shader_program) stone3.scale(3.5, 0.5, 0.7) stone3.rotate(0.0, 32.5, 0.0) stone3.translate(5.7, 6.5, 7.2) self.objects.append(stone3) stone4 = Stone(self.stone_shader_program) stone4.scale(1.5, 3.0, 0.7) stone4.rotate(0.0, -25.0, 0.0) stone4.translate(-4.5, 3.0, 8.2) self.objects.append(stone4) stone5 = Stone(self.stone_shader_program) stone5.scale(1.5, 3.0, 0.7) stone5.rotate(0.0, -40.0, 0.0) stone5.translate(-8.5, 3.0, 5.7) self.objects.append(stone5) stone6 = Stone(self.stone_shader_program) stone6.scale(1.5, 3.0, 0.7) stone6.rotate(0.0, -55.0, 0.0) stone6.translate(-11.8, 3.0, 2.0) self.objects.append(stone6) stone7 = Stone(self.stone_shader_program) stone7.scale(2.7, 0.5, 0.7) stone7.rotate(0.0, -32.5, 0.0) stone7.translate(-6.0, 6.5, 7.4) self.objects.append(stone7) stone8 = Stone(self.stone_shader_program) stone8.scale(2.7, 0.5, 0.7) stone8.rotate(0.0, -47.5, 0.0) stone8.translate(-10.5, 6.5, 3.7) self.objects.append(stone8) stone9 = Stone(self.stone_shader_program) stone9.scale(1.5, 3.2, 0.7) stone9.rotate(0.0, 77.0, 0.0) stone9.translate(11.5, 3.2, 0.8) self.objects.append(stone9) stone10 = Stone(self.stone_shader_program) stone10.scale(2.2, 4.0, 1.0) stone10.rotate(0.0, 3.0, 0.0) stone10.translate(-3.0, 4.0, -6.0) self.objects.append(stone10) stone11 = Stone(self.stone_shader_program) stone11.scale(2.2, 4.0, 1.0) stone11.rotate(0.0, -3.0, 0.0) stone11.translate(3.0, 4.0, -6.0) self.objects.append(stone11) stone12 = Stone(self.stone_shader_program) stone12.scale(5.5, 0.6, 0.9) stone12.rotate(0.0, 0.0, 0.0) stone12.translate(0.0, 8.6, -6.0) self.objects.append(stone12) def setup_boulders(self): """ Creates objects for the boulders present along Stonehenge and applies model transforms to them. """ boulder1 = Boulder(self.stone_shader_program) boulder1.scale(1.0, 1.0, 1.0) boulder1.translate(10.0, -0.2, -7.5) self.objects.append(boulder1) boulder2 = Boulder(self.stone_shader_program) boulder2.scale(1.3, 1.3, 1.3) boulder2.translate(8.0, -0.4, -9.0) self.objects.append(boulder2) boulder3 = Boulder(self.stone_shader_program) boulder3.scale(2.0, 2.0, 2.0) boulder3.translate(-0.8, -1.0, 1.3) self.objects.append(boulder3) boulder4 = Boulder(self.stone_shader_program) boulder4.scale(1.7, 1.7, 1.7) boulder4.translate(-4.1, -0.8, 0.1) self.objects.append(boulder4) boulder5 = Boulder(self.stone_shader_program) boulder5.scale(1.3, 1.3, 1.3) boulder5.translate(2.8, -0.5, 0.5) self.objects.append(boulder5) def display(self): """ Displays the scene on the current OpenGL window. """ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) # Set up parameters for the ground glUseProgram(self.ground_shader_program) self.camera.setup(self.ground_shader_program) self.light.setup(self.ground_shader_program) self.objects[0].draw(self.ground_shader_program) # Set up parameters for the stones and boulders glUseProgram(self.stone_shader_program) self.camera.setup(self.stone_shader_program) self.light.setup(self.stone_shader_program) for obj in self.objects[1:]: obj.draw(self.stone_shader_program) glutSwapBuffers() def handle_key(self, *args): """ Handles user input to rotate the scene or quit. :param *args: User input parameters passed by GLUT """ key = args[0].decode() # Rotate the scene clockwise if key == 'a': for obj in self.objects: obj.rotate(0, 2, 0) # Rotate the scene counter-clockwise elif key == 'd': for obj in self.objects: obj.rotate(0, -2, 0) # Close the window elif key == 'q': sys.exit(0) self.display()