Initial release

This commit is contained in:
bartvbl 2019-02-04 18:32:08 +01:00
commit a430863701
35 changed files with 1925 additions and 0 deletions

32
.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app

27
.gitmodules vendored Normal file
View File

@ -0,0 +1,27 @@
[submodule "lib/glad"]
path = lib/glad
url = https://github.com/Dav1dde/glad.git
branch = c
[submodule "lib/glfw"]
path = lib/glfw
url = https://github.com/glfw/glfw.git
branch = master
[submodule "lib/glm"]
path = lib/glm
url = https://github.com/g-truc/glm.git
branch = master
[submodule "lib/stb"]
path = lib/stb
url = https://github.com/nothings/stb.git
branch = master
[submodule "lib/arrrgh"]
path = lib/arrrgh
url = https://github.com/ElectricToy/arrrgh.git
branch = master
[submodule "lib/SFML"]
path = lib/SFML
url = https://github.com/SFML/SFML.git
branch = master
[submodule "arrrgh"]
path = arrrgh
url = https://github.com/ElectricToy/arrrgh.git

90
CMakeLists.txt Normal file
View File

@ -0,0 +1,90 @@
#
# Specify minimum CMake version and project name
#
cmake_minimum_required (VERSION 3.0)
project (glowbox)
#
# CMake setup
#
set (CMAKE_CXX_STANDARD 17)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set (CMAKE_VERBOSE_MAKEFILE 0) # 1 should be used for debugging
set (CMAKE_SUPPRESS_REGENERATION TRUE) # Suppresses ZERO_CHECK
if(MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic")
if(NOT WIN32)
set(GLAD_LIBRARIES dl)
endif()
endif()
#
# GLFW options
#
option (GLFW_INSTALL OFF)
option (GLFW_BUILD_DOCS OFF)
option (GLFW_BUILD_EXAMPLES OFF)
option (GLFW_BUILD_TESTS OFF)
add_subdirectory (lib/glfw)
# SFML options
# We only need sound for this project, so everything else can be turned off
option (SFML_BUILD_GRAPHICS OFF)
option (SFML_BUILD_WINDOW OFF)
option (SFML_BUILD_NETWORK OFF)
add_subdirectory(lib/SFML)
#
# Set include paths
#
include_directories (src/
lib/glad/include/
lib/glfw/include/
lib/glm/
lib/stb/
lib/arrrgh/
lib/SFML/include/)
#
# Add files
#
file (GLOB VENDORS_SOURCES lib/glad/src/glad.c)
file (GLOB_RECURSE PROJECT_HEADERS src/*.hpp
src/*.h)
file (GLOB_RECURSE PROJECT_SOURCES src/*.cpp
src/*.cxx
src/*.cc
src/*.c)
file (GLOB_RECURSE PROJECT_SHADERS res/shaders/*.comp
res/shaders/*.frag
res/shaders/*.geom
res/shaders/*.vert)
file (GLOB PROJECT_CONFIGS CMakeLists.txt
README.rst
.gitignore
.gitmodules)
#
# Organizing files
#
source_group ("headers" FILES ${PROJECT_HEADERS})
source_group ("shaders" FILES ${PROJECT_SHADERS})
source_group ("sources" FILES ${PROJECT_SOURCES})
source_group ("libraries" FILES ${VENDORS_SOURCES})
#
# Set executable and target link libraries
#
add_definitions (-DGLFW_INCLUDE_NONE
-DPROJECT_SOURCE_DIR=\"${PROJECT_SOURCE_DIR}\")
add_executable (${PROJECT_NAME} ${PROJECT_SOURCES} ${PROJECT_HEADERS}
${PROJECT_SHADERS} ${PROJECT_CONFIGS}
${VENDORS_SOURCES})
target_link_libraries (${PROJECT_NAME}
glfw
sfml-audio
${GLFW_LIBRARIES}
${GLAD_LIBRARIES})

0
build/.emptydirectory Normal file
View File

1
lib/SFML Submodule

@ -0,0 +1 @@
Subproject commit b516a3ae2bd4b673e7e61c8fe3fd3deba79ebcc0

1
lib/arrrgh Submodule

@ -0,0 +1 @@
Subproject commit a4c3a105fb2c718556a5b71fe61f67c75eaa8bc1

View File

@ -0,0 +1,3 @@
!#/bin/bash
cd glad
python3 -m glad --profile core --out-path . --generator c --spec gl

3
lib/genglad_WINDOWS.ps1 Normal file
View File

@ -0,0 +1,3 @@
cd glad
python -m glad --profile core --out-path . --generator c --spec gl
pause

1
lib/glad Submodule

@ -0,0 +1 @@
Subproject commit 8dcc73c4f5095362c44ca7f8b7e39374849d8e05

1
lib/glfw Submodule

@ -0,0 +1 @@
Subproject commit 29d8ca4ce4aef8e0afca62b8ace9ee18a24a9ffd

1
lib/glm Submodule

@ -0,0 +1 @@
Subproject commit 8cd6db11cd8d2bf68aaad86097f15a9f94604dc4

1
lib/stb Submodule

@ -0,0 +1 @@
Subproject commit e6afb9cbae4064da8c3e69af3ff5c4629579c1d2

View File

@ -0,0 +1 @@
sudo apt install libopenal-dev libvorbis-dev libflac-dev xorg-dev

5
res/CREDITS.txt Normal file
View File

@ -0,0 +1,5 @@
I borrowed the Hall of the Mountain King track from here:
Hall of the Mountain King Kevin MacLeod (incompetech.com)
Licensed under Creative Commons: By Attribution 3.0 License
http://creativecommons.org/licenses/by/3.0/

Binary file not shown.

12
res/shaders/simple.frag Normal file
View File

@ -0,0 +1,12 @@
#version 430 core
in layout(location = 0) vec3 normal;
out vec4 color;
void main()
{
float intensity = dot(normalize(normal), vec3(0, 0, 1));
color = vec4(0.5 * normal + 0.5, 1.0);
//color = vec4(intensity, intensity, intensity, 1.0f);
}

14
res/shaders/simple.vert Normal file
View File

@ -0,0 +1,14 @@
#version 430 core
in layout(location = 0) vec3 position;
in layout(location = 1) vec3 normal_in;
uniform layout(location = 3) mat4 MVP;
out layout(location = 0) vec3 normal_out;
void main()
{
normal_out = normal_in;
gl_Position = MVP * vec4(position, 1.0f);
}

343
src/gamelogic.cpp Normal file
View File

@ -0,0 +1,343 @@
#include <chrono>
#include <GLFW/glfw3.h>
#include <glad/glad.h>
#include <SFML/Audio/SoundBuffer.hpp>
#include <utilities/shader.hpp>
#include <glm/vec3.hpp>
#include <iostream>
#include <utilities/timeutils.h>
#include <utilities/mesh.h>
#include <utilities/shapes.h>
#include <utilities/glutils.h>
#include <SFML/Audio/Sound.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "gamelogic.h"
#include "sceneGraph.hpp"
enum KeyFrameAction {
BOTTOM, TOP
};
#include <timestamps.h>
double padPositionX = 0;
double padPositionY = 0;
unsigned int currentKeyFrame = 0;
unsigned int previousKeyFrame = 0;
SceneNode* rootNode;
SceneNode* boxNode;
SceneNode* ballNode;
SceneNode* padNode;
double ballRadius = 3.0f;
// These are heap allocated, because they should not be initialised at the start of the program
sf::SoundBuffer* buffer;
Gloom::Shader* shader;
sf::Sound* sound;
const glm::vec3 boxDimensions(180, 90, 50);
const glm::vec3 padDimensions(30, 3, 40);
glm::vec3 ballPosition(0, ballRadius + padDimensions.y, boxDimensions.z / 2);
glm::vec3 ballDirection(1, 1, 0.02f);
const float BallVerticalTravelDistance = boxDimensions.y - 2.0 * ballRadius - padDimensions.y;
CommandLineOptions options;
bool hasStarted = false;
bool hasLost = false;
bool jumpedToNextFrame = false;
// Modify if you want the music to start further on in the track. Measured in seconds.
const float debug_startTime = 0;
double totalElapsedTime = debug_startTime;
void mouseCallback(GLFWwindow* window, double x, double y) {
int windowWidth, windowHeight;
glfwGetWindowSize(window, &windowWidth, &windowHeight);
glViewport(0, 0, windowWidth, windowHeight);
padPositionX = x / double(windowWidth);
padPositionY = y / double(windowHeight);
if(padPositionX > 1) {
padPositionX = 1;
glfwSetCursorPos(window, windowWidth, y);
} else if(padPositionX < 0) {
padPositionX = 0;
glfwSetCursorPos(window, 0, y);
}
if(padPositionY > 1) {
padPositionY = 1;
glfwSetCursorPos(window, x, windowHeight);
} else if(padPositionY < 0) {
padPositionY = 0;
glfwSetCursorPos(window, x, 0);
}
}
void initGame(GLFWwindow* window, CommandLineOptions gameOptions) {
buffer = new sf::SoundBuffer();
if (!buffer->loadFromFile("../res/Hall of the Mountain King.ogg")) {
return;
}
options = gameOptions;
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
glfwSetCursorPosCallback(window, mouseCallback);
shader = new Gloom::Shader();
shader->makeBasicShader("../res/shaders/simple.vert", "../res/shaders/simple.frag");
shader->activate();
Mesh box = generateBox(boxDimensions.x, boxDimensions.y, boxDimensions.z, true);
Mesh pad = generateBox(padDimensions.x, padDimensions.y, padDimensions.z, false);
Mesh sphere = generateSphere(1.0, 40, 40);
unsigned int ballVAO = generateBuffer(sphere);
unsigned int boxVAO = generateBuffer(box);
unsigned int padVAO = generateBuffer(pad);
rootNode = createSceneNode();
boxNode = createSceneNode();
padNode = createSceneNode();
ballNode = createSceneNode();
rootNode->children.push_back(boxNode);
rootNode->children.push_back(padNode);
rootNode->children.push_back(ballNode);
boxNode->vertexArrayObjectID = boxVAO;
boxNode->VAOIndexCount = box.indices.size();
padNode->vertexArrayObjectID = padVAO;
padNode->VAOIndexCount = pad.indices.size();
ballNode->vertexArrayObjectID = ballVAO;
ballNode->VAOIndexCount = sphere.indices.size();
getTimeDeltaSeconds();
std::cout << "Ready. Click to start!" << std::endl;
}
void updateNodeTransformations(SceneNode* node, glm::mat4 transformationThusFar) {
glm::mat4 transformationMatrix(1.0);
switch(node->nodeType) {
case GEOMETRY:
transformationMatrix =
glm::translate(glm::mat4(1.0), node->position)
* glm::translate(glm::mat4(1.0), node->referencePoint)
* glm::rotate(glm::mat4(1.0), node->rotation.z, glm::vec3(0,0,1))
* glm::rotate(glm::mat4(1.0), node->rotation.y, glm::vec3(0,1,0))
* glm::rotate(glm::mat4(1.0), node->rotation.x, glm::vec3(1,0,0))
* glm::translate(glm::mat4(1.0), -node->referencePoint)
* glm::scale(glm::mat4(1.0), node->scale);
break;
case POINT_LIGHT:
break;
case SPOT_LIGHT:
break;
}
node->currentTransformationMatrix = transformationThusFar * transformationMatrix;
for(SceneNode* child : node->children) {
updateNodeTransformations(child, node->currentTransformationMatrix);
}
}
void updateFrame(GLFWwindow* window) {
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
double timeDelta = getTimeDeltaSeconds();
if(!hasStarted) {
if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1)) {
if (options.enableMusic) {
sound = new sf::Sound();
sound->setBuffer(*buffer);
sf::Time startTime = sf::seconds(debug_startTime);
sound->setPlayingOffset(startTime);
sound->play();
}
totalElapsedTime = debug_startTime;
hasStarted = true;
}
ballPosition.x = (1 - padPositionX) * (boxDimensions.x - padDimensions.x) + padDimensions.x / 2.0;
ballPosition.y = ballRadius + padDimensions.y;
} else {
// I really should calculate this using the std::chrono timestamp for this
// You definitely end up with a cumulative error when doing lots of small additions like this
// However, for a game that lasts only a few minutes this is fine.
totalElapsedTime += timeDelta;
if(hasLost) {
ballRadius += 200 * timeDelta;
if(ballRadius > 999999) {
ballRadius = 999999;
}
} else {
for (unsigned int i = currentKeyFrame; i < keyFrameTimeStamps.size(); i++) {
if (totalElapsedTime < keyFrameTimeStamps.at(i)) {
continue;
}
currentKeyFrame = i;
}
jumpedToNextFrame = currentKeyFrame != previousKeyFrame;
previousKeyFrame = currentKeyFrame;
double frameStart = keyFrameTimeStamps.at(currentKeyFrame);
double frameEnd = keyFrameTimeStamps.at(currentKeyFrame + 1); // Assumes last keyframe at infinity
double elapsedTimeInFrame = totalElapsedTime - frameStart;
double frameDuration = frameEnd - frameStart;
double fractionFrameComplete = elapsedTimeInFrame / frameDuration;
double ballYCoord;
const float ballBottomY = ballRadius + padDimensions.y;
KeyFrameAction currentOrigin = keyFrameDirections.at(currentKeyFrame);
KeyFrameAction currentDestination = keyFrameDirections.at(currentKeyFrame + 1);
if (currentOrigin == BOTTOM && currentDestination == BOTTOM) {
ballYCoord = ballBottomY;
} else if (currentOrigin == TOP && currentDestination == TOP) {
ballYCoord = ballBottomY + BallVerticalTravelDistance;
} else if (currentDestination == BOTTOM) {
ballYCoord = ballBottomY + BallVerticalTravelDistance * (1 - fractionFrameComplete);
} else if (currentDestination == TOP) {
ballYCoord = ballBottomY + BallVerticalTravelDistance * fractionFrameComplete;
}
const float ballSpeed = 60.0f;
ballPosition.x += timeDelta * ballSpeed * ballDirection.x;
ballPosition.y = ballYCoord;
ballPosition.z += timeDelta * ballSpeed * ballDirection.z;
if (ballPosition.x + ballRadius > boxDimensions.x) {
// Crude approximation, because it does not compute the intersection with the wall
// Not doing it causes the ball to get stuck in the wall though
ballPosition.x = boxDimensions.x - ballRadius;
ballDirection.x *= -1;
} else if (ballPosition.x - ballRadius < 0) {
ballPosition.x = ballRadius;
ballDirection.x *= -1;
}
if (ballPosition.y + ballRadius > boxDimensions.y) {
ballPosition.y = boxDimensions.y - ballRadius;
ballDirection.y *= -1;
} else if (ballPosition.y - ballRadius < 0) {
ballPosition.y = ballRadius;
ballDirection.y *= -1;
}
if (ballPosition.z + ballRadius > boxDimensions.z) {
ballPosition.z = boxDimensions.z - ballRadius;
ballDirection.z *= -1;
} else if (ballPosition.z - ballRadius < 0) {
ballPosition.z = ballRadius;
ballDirection.z *= -1;
}
if(options.enableAutoplay) {
padPositionX = 1 - (ballPosition.x / (boxDimensions.x - 2 * ballRadius));
padPositionY = 1 - (ballPosition.z / (boxDimensions.z - 2 * ballRadius));
}
// Check if the ball is hitting the pad when the ball is at the bottom.
// If not, you just lost the game! (hehe)
if (jumpedToNextFrame && currentOrigin == BOTTOM && currentDestination == TOP) {
double padLeftXCoordinate = (1 - padPositionX) * (boxDimensions.x - padDimensions.x);
double padRightXCoordinate = padLeftXCoordinate + padDimensions.x;
double padFrontZCoordinate = (1 - padPositionY) * (boxDimensions.z - padDimensions.z);
double padBackZCoordinate = padFrontZCoordinate + padDimensions.z;
if (ballPosition.x < padLeftXCoordinate
|| ballPosition.x > padRightXCoordinate
|| ballPosition.z < padFrontZCoordinate
|| ballPosition.z > padBackZCoordinate) {
hasLost = true;
if (options.enableMusic) {
sound->stop();
}
}
}
}
}
glm::mat4 projection = glm::perspective(glm::radians(90.0f), float(windowWidth) / float(windowHeight), 0.1f,
120.f);
glm::mat4 cameraTransform = glm::translate(glm::mat4(1), glm::vec3(0, 0, 0))
* glm::rotate(glm::mat4(1.0), 0.2f, glm::vec3(1, 0, 0))
* glm::rotate(glm::mat4(1.0), float(M_PI), glm::vec3(0, 1, 0));
glm::mat4 VP = projection * cameraTransform;
updateNodeTransformations(rootNode, VP);
boxNode->position = {-boxDimensions.x / 2, -boxDimensions.y / 2 - 15, boxDimensions.z - 10};
padNode->position = {-boxDimensions.x / 2 + (1 - padPositionX) * (boxDimensions.x - padDimensions.x),
-boxDimensions.y / 2 - 15,
boxDimensions.z - 10 + (1 - padPositionY) * (boxDimensions.z - padDimensions.z)};
ballNode->position = {-boxDimensions.x / 2 + ballPosition.x,
-boxDimensions.y / 2 - 15 + ballPosition.y,
boxDimensions.z - 10 + ballPosition.z};
ballNode->scale = {ballRadius, ballRadius, ballRadius};
}
void renderNode(SceneNode* node) {
glUniformMatrix4fv(3, 1, GL_FALSE, glm::value_ptr(node->currentTransformationMatrix));
switch(node->nodeType) {
case GEOMETRY:
if(node->vertexArrayObjectID != -1) {
glBindVertexArray(node->vertexArrayObjectID);
glDrawElements(GL_TRIANGLES, node->VAOIndexCount, GL_UNSIGNED_INT, nullptr);
}
break;
case POINT_LIGHT:
break;
case SPOT_LIGHT:
break;
}
for(SceneNode* child : node->children) {
renderNode(child);
}
}
void renderFrame(GLFWwindow* window) {
int windowWidth, windowHeight;
glfwGetWindowSize(window, &windowWidth, &windowHeight);
glViewport(0, 0, windowWidth, windowHeight);
renderNode(rootNode);
}

8
src/gamelogic.h Normal file
View File

@ -0,0 +1,8 @@
#pragma once
#include <utilities/window.hpp>
void initGame(GLFWwindow* window, CommandLineOptions options);
void updateFrame(GLFWwindow* window);
void renderFrame(GLFWwindow* window);

112
src/main.cpp Normal file
View File

@ -0,0 +1,112 @@
// Local headers
#include "utilities/window.hpp"
#include "program.hpp"
// System headers
#include <glad/glad.h>
#include <GLFW/glfw3.h>
// Standard headers
#include <cstdlib>
#include <arrrgh.hpp>
// A callback which allows GLFW to report errors whenever they occur
static void glfwErrorCallback(int error, const char *description)
{
fprintf(stderr, "GLFW returned an error:\n\t%s (%i)\n", description, error);
}
GLFWwindow* initialise()
{
// Initialise GLFW
if (!glfwInit())
{
fprintf(stderr, "Could not start GLFW\n");
exit(EXIT_FAILURE);
}
// Set core window options (adjust version numbers if needed)
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Enable the GLFW runtime error callback function defined previously.
glfwSetErrorCallback(glfwErrorCallback);
// Set additional window options
glfwWindowHint(GLFW_RESIZABLE, windowResizable);
glfwWindowHint(GLFW_SAMPLES, windowSamples); // MSAA
// Create window using GLFW
GLFWwindow* window = glfwCreateWindow(windowWidth,
windowHeight,
windowTitle.c_str(),
nullptr,
nullptr);
// Ensure the window is set up correctly
if (!window)
{
fprintf(stderr, "Could not open GLFW window\n");
glfwTerminate();
exit(EXIT_FAILURE);
}
// Let the window be the current OpenGL context and initialise glad
glfwMakeContextCurrent(window);
gladLoadGL();
// Print various OpenGL information to stdout
printf("%s: %s\n", glGetString(GL_VENDOR), glGetString(GL_RENDERER));
printf("GLFW\t %s\n", glfwGetVersionString());
printf("OpenGL\t %s\n", glGetString(GL_VERSION));
printf("GLSL\t %s\n\n", glGetString(GL_SHADING_LANGUAGE_VERSION));
return window;
}
int main(int argc, const char* argb[])
{
arrrgh::parser parser("glowbox", "Small breakout like juggling game");
const auto& showHelp = parser.add<bool>("help", "Show this help message.", 'h', arrrgh::Optional, false);
const auto& enableMusic = parser.add<bool>("enable-music", "Play background music while the game is playing", 'm', arrrgh::Optional, false);
const auto& enableAutoplay = parser.add<bool>("autoplay", "Let the game play itself automatically. Useful for testing.", 'a', arrrgh::Optional, false);
// If you want to add more program arguments, define them here,
// but do not request their value here (they have not been parsed yet at this point).
try
{
parser.parse(argc, argb);
}
catch (const std::exception& e)
{
std::cerr << "Error parsing arguments: " << e.what() << std::endl;
parser.show_usage(std::cerr);
exit(1);
}
// Show help if desired
if(showHelp.value())
{
return 0;
}
CommandLineOptions options;
options.enableMusic = enableMusic.value();
options.enableAutoplay = enableAutoplay.value();
// Initialise window using GLFW
GLFWwindow* window = initialise();
// Run an OpenGL application using this window
runProgram(window, options);
// Terminate GLFW (no need to call glfwDestroyWindow)
glfwTerminate();
return EXIT_SUCCESS;
}

64
src/program.cpp Normal file
View File

@ -0,0 +1,64 @@
// Local headers
#include "program.hpp"
#include "utilities/window.hpp"
#include "gamelogic.h"
#include <glm/glm.hpp>
// glm::translate, glm::rotate, glm::scale, glm::perspective
#include <glm/gtc/matrix_transform.hpp>
#include <iostream>
#include <SFML/Audio.hpp>
#include <SFML/System/Time.hpp>
#include <utilities/shapes.h>
#include <utilities/glutils.h>
#include <utilities/shader.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <utilities/timeutils.h>
void runProgram(GLFWwindow* window, CommandLineOptions options)
{
// Enable depth (Z) buffer (accept "closest" fragment)
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
// Configure miscellaneous OpenGL settings
glEnable(GL_CULL_FACE);
// Set default colour after clearing the colour buffer
glClearColor(0.3f, 0.5f, 0.8f, 1.0f);
initGame(window, options);
// Rendering Loop
while (!glfwWindowShouldClose(window))
{
// Clear colour and depth buffers
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
updateFrame(window);
renderFrame(window);
// Handle other events
glfwPollEvents();
handleKeyboardInput(window);
// Flip buffers
glfwSwapBuffers(window);
}
}
void handleKeyboardInput(GLFWwindow* window)
{
// Use escape key for terminating the GLFW window
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
{
glfwSetWindowShouldClose(window, GL_TRUE);
}
}

59
src/program.hpp Normal file
View File

@ -0,0 +1,59 @@
#ifndef PROGRAM_HPP
#define PROGRAM_HPP
#pragma once
// System headers
#include <GLFW/glfw3.h>
#include <glad/glad.h>
#include <string>
#include <utilities/window.hpp>
// Main OpenGL program
void runProgram(GLFWwindow* window, CommandLineOptions options);
// Function for handling keypresses
void handleKeyboardInput(GLFWwindow* window);
// Checks for whether an OpenGL error occurred. If one did,
// it prints out the error type and ID
inline void printGLError() {
int errorID = glGetError();
if(errorID != GL_NO_ERROR) {
std::string errorString;
switch(errorID) {
case GL_INVALID_ENUM:
errorString = "GL_INVALID_ENUM";
break;
case GL_INVALID_OPERATION:
errorString = "GL_INVALID_OPERATION";
break;
case GL_INVALID_FRAMEBUFFER_OPERATION:
errorString = "GL_INVALID_FRAMEBUFFER_OPERATION";
break;
case GL_OUT_OF_MEMORY:
errorString = "GL_OUT_OF_MEMORY";
break;
case GL_STACK_UNDERFLOW:
errorString = "GL_STACK_UNDERFLOW";
break;
case GL_STACK_OVERFLOW:
errorString = "GL_STACK_OVERFLOW";
break;
default:
errorString = "[Unknown error ID]";
break;
}
fprintf(stderr, "An OpenGL error occurred (%i): %s.\n",
errorID, errorString.c_str());
}
}
#endif

29
src/sceneGraph.cpp Normal file
View File

@ -0,0 +1,29 @@
#include "sceneGraph.hpp"
#include <iostream>
SceneNode* createSceneNode() {
return new SceneNode();
}
// Add a child node to its parent's list of children
void addChild(SceneNode* parent, SceneNode* child) {
parent->children.push_back(child);
}
// Pretty prints the current values of a SceneNode instance to stdout
void printNode(SceneNode* node) {
printf(
"SceneNode {\n"
" Child count: %i\n"
" Rotation: (%f, %f, %f)\n"
" Location: (%f, %f, %f)\n"
" Reference point: (%f, %f, %f)\n"
" VAO ID: %i\n"
"}\n",
int(node->children.size()),
node->rotation.x, node->rotation.y, node->rotation.z,
node->position.x, node->position.y, node->position.z,
node->referencePoint.x, node->referencePoint.y, node->referencePoint.z,
node->vertexArrayObjectID);
}

67
src/sceneGraph.hpp Normal file
View File

@ -0,0 +1,67 @@
#pragma once
#include <glm/glm.hpp>
#include <glm/mat4x4.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <stack>
#include <vector>
#include <cstdio>
#include <stdbool.h>
#include <cstdlib>
#include <ctime>
#include <chrono>
#include <fstream>
enum SceneNodeType {
GEOMETRY, POINT_LIGHT, SPOT_LIGHT
};
// In case you haven't got much experience with C or C++, let me explain this "typedef" you see below.
// The point of a typedef is that you it, as its name implies, allows you to define arbitrary data types based upon existing ones. For instance, "typedef float typeWhichMightBeAFloat;" allows you to define a variable such as this one: "typeWhichMightBeAFloat variableName = 5.0;". The C/C++ compiler translates this type into a float.
// What is the point of using it here? A smrt person, while designing the C language, thought it would be a good idea for various reasons to force you to explicitly state that you are using a data structure datatype (struct). So, when defining a variable, you'd have to type "struct SceneNode node = ..." in the case of a SceneNode. Which can get in the way of readability.
// If we just use typedef to define a new type called "SceneNode", which really is the type "struct SceneNode", we can omit the "struct" part when creating an instance of SceneNode.
struct SceneNode {
SceneNode() {
position = glm::vec3(0, 0, 0);
rotation = glm::vec3(0, 0, 0);
scale = glm::vec3(1, 1, 1);
referencePoint = glm::vec3(0, 0, 0);
vertexArrayObjectID = -1;
VAOIndexCount = 0;
nodeType = GEOMETRY;
}
// A list of all children that belong to this node.
// For instance, in case of the scene graph of a human body shown in the assignment text, the "Upper Torso" node would contain the "Left Arm", "Right Arm", "Head" and "Lower Torso" nodes in its list of children.
std::vector<SceneNode*> children;
// The node's position and rotation relative to its parent
glm::vec3 position;
glm::vec3 rotation;
glm::vec3 scale;
// A transformation matrix representing the transformation of the node's location relative to its parent. This matrix is updated every frame.
glm::mat4 currentTransformationMatrix;
// The location of the node's reference point
glm::vec3 referencePoint;
// The ID of the VAO containing the "appearance" of this SceneNode.
int vertexArrayObjectID;
unsigned int VAOIndexCount;
// Node type is used to determine how to handle the contents of a node
SceneNodeType nodeType;
};
// Struct for keeping track of 2D coordinates
SceneNode* createSceneNode();
void addChild(SceneNode* parent, SceneNode* child);
void printNode(SceneNode* node);
// For more details, see SceneGraph.cpp.

361
src/timestamps.h Normal file
View File

@ -0,0 +1,361 @@
#pragma once
// I recommend closing this file right now.
// You'll only find despair here
// And cries of "WHY"
// Proceed at your own risk.
const std::vector<double> keyFrameTimeStamps =
{0, 0.98,
1.570, 2.102, // block 0
2.658, 3.229,
3.781, 4.349,
4.926, 5.562,
6.083, 6.630,
7.177, 7.733,
8.343, 8.838,
9.436, 9.974,
10.506, 11.086,
11.660, 12.210,
12.729, 13.333,
13.916, 14.402,
14.979, 15.526,
16.102, 16.611,
17.158, 17.725,
18.285, 18.864,
19.408, 19.963,
20.482, 21.041,
21.585, 22.141,
22.697, 23.286,
23.808, 24.340,
24.750, 25.447,
26.000, 26.575,
27.134, 27.684,
28.227, 28.775,
29.359, 29.928,
30.485, 31.076,
31.603, 32.176,
32.721, 33.284,
33.808, 34.388,
34.962, 35.514,
36.125, 36.704,
37.281, 37.849,
38.370, 38.948,
39.500, 40.009,
40.551, 41.198,
41.692, 42.274,
42.840, 43.383,
43.940, 44.516,
45.062, 45.610,
46.189, 46.732,
47.314, 47.875,
48.441, 49.023,
49.589, 50.138,
50.675, 51.237,
51.767, 52.312,
52.882, 53.439,
53.970, 54.578,
55.121, 55.592,
56.112, 56.691,
57.179, 57.663, // block 5
58.223, 58.735,
59.293, 59.790,
60.327, 60.822,
61.328, 61.862,
62.375, 62.869,
63.362, 63.830,
64.365, 64.880,
65.342, 65.900,
66.386, 66.883,
67.375, 67.860,
68.364, 68.835,
69.343, 69.970,
70.340, 70.857,
71.301, 71.860,
72.301, 72.768,
73.257, 73.732,
74.213, 74.685,
75.148, 75.649,
76.127, 76.573,
77.049, 77.514,
77.986, 78.433,
78.898, 79.384,
79.850, 80.309,
80.746, 81.208,
81.638, 82.057,
82.503, 82.946,
83.410, 83.847,
84.263, 84.683,
85.169, 85.551,
85.979, 86.417,
86.862, 87.272,
87.715, 88.121,
88.538, 88.944,
89.404, 89.761,
90.214, 90.610,
91.034, 91.427,
91.842, 92.239,
92.645, 92.991,
93.359, 93.722,
94.095, 94.495,
94.874, 95.242,
95.605, 95.978,
96.351, 96.714,
97.098, 97.461,
97.883, 98.240,
98.602, 98.986,
99.468, 99.760,
100.123, 100.442,
100.831, 101.189,
101.611, 102.011, // Block 10
102.336, 102.612,
102.953, 103.331,
103.710, 104.046,
104.408, 104.787,
105.187, 105.479,
105.847, 106.188,
106.475, 106.854,
107.162, 107.536,
107.968, 108.239,
108.661, 108.953,
109.262, 109.581,
109.927, 110.273,
110.652, 110.988,
111.312, 111.642,
112.000, 112.330,
112.660, 113.033,
113.314, 113.639,
113.958, 114.310,
114.656, 115.040,
115.365, 115.690,
116.031, 116.350,
116.669, 116.999,
117.302, 117.638,
117.962, 118.287,
118.628, 118.925,
119.250, 119.569,
119.937, 120.257,
120.565, 120.868,
121.252, 121.533,
121.880, 122.210,
122.540, 122.827,
123.146, 123.427,
123.757, 124.060,
124.407, 124.715,
125.023, 125.364,
125.673, 125.927,
126.273, 126.555,
126.896, 127.204,
127.491, 127.788,
128.097, 128.378,
128.714, 128.963,
129.325, 129.569,
129.872, 130.164,
130.667, 130.877, 131.132,
133.654, 133.842, 134.054,
134.568, 134.768, 135.034,
137.468, 137.707, 137.939,
138.388, 138.648, 138.902,
139.346, 139.590, 139.908,
139.379, 139.590, 139.871,
140.098, 140.352,
140.574, 140.850,
141.105, 141.343,
144.286, 144.367, 144.595,
9999999};
const std::vector<KeyFrameAction> keyFrameDirections =
{BOTTOM, TOP,
BOTTOM, TOP, // Block 0
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP, // Block 5
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP, // Block 10
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
TOP, BOTTOM, TOP,
TOP, BOTTOM, TOP,
TOP, BOTTOM, TOP,
TOP, BOTTOM, TOP,
TOP, BOTTOM, TOP,
TOP, BOTTOM, TOP,
TOP, BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
BOTTOM, TOP,
TOP, BOTTOM, TOP,
BOTTOM,};

200
src/utilities/camera.hpp Normal file
View File

@ -0,0 +1,200 @@
#ifndef CAMERA_HPP
#define CAMERA_HPP
#pragma once
// System headers
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/quaternion.hpp>
#include <GLFW/glfw3.h>
namespace Gloom
{
class Camera
{
public:
Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 2.0f),
GLfloat movementSpeed = 5.0f,
GLfloat mouseSensitivity = 0.005f)
{
cPosition = position;
cMovementSpeed = movementSpeed;
cMouseSensitivity = mouseSensitivity;
// Set up the initial view matrix
updateViewMatrix();
}
// Public member functions
/* Getter for the view matrix */
glm::mat4 getViewMatrix() { return matView; }
/* Handle keyboard inputs from a callback mechanism */
void handleKeyboardInputs(int key, int action)
{
// Keep track of pressed/released buttons
if (key >= 0 && key < 512)
{
if (action == GLFW_PRESS)
{
keysInUse[key] = true;
}
else if (action == GLFW_RELEASE)
{
keysInUse[key] = false;
}
}
}
/* Handle mouse button inputs from a callback mechanism */
void handleMouseButtonInputs(int button, int action)
{
// Ensure that the camera only rotates when the left mouse button is
// pressed
if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS)
{
isMousePressed = true;
}
else
{
isMousePressed = false;
resetMouse = true;
}
}
/* Handle cursor position from a callback mechanism */
void handleCursorPosInput(double xpos, double ypos)
{
// Do nothing if the left mouse button is not pressed
if (isMousePressed == false)
return;
// There should be no movement when the mouse button is released
if (resetMouse)
{
lastXPos = xpos;
lastYPos = ypos;
resetMouse = false;
}
// Keep track of pitch and yaw for the current frame
fYaw = xpos - lastXPos;
fPitch = ypos - lastYPos;
// Update last known cursor position
lastXPos = xpos;
lastYPos = ypos;
}
/* Update the camera position and view matrix
`deltaTime` is the time between the current and last frame */
void updateCamera(GLfloat deltaTime)
{
// Extract movement information from the view matrix
glm::vec3 dirX(matView[0][0], matView[1][0], matView[2][0]);
glm::vec3 dirY(matView[0][1], matView[1][1], matView[2][1]);
glm::vec3 dirZ(matView[0][2], matView[1][2], matView[2][2]);
// Alter position in the appropriate direction
glm::vec3 fMovement(0.0f, 0.0f, 0.0f);
if (keysInUse[GLFW_KEY_W]) // forward
fMovement -= dirZ;
if (keysInUse[GLFW_KEY_S]) // backward
fMovement += dirZ;
if (keysInUse[GLFW_KEY_A]) // left
fMovement -= dirX;
if (keysInUse[GLFW_KEY_D]) // right
fMovement += dirX;
if (keysInUse[GLFW_KEY_E]) // vertical up
fMovement += dirY;
if (keysInUse[GLFW_KEY_Q]) // vertical down
fMovement -= dirY;
// Trick to balance PC speed with movement
GLfloat velocity = cMovementSpeed * deltaTime;
// Update camera position using the appropriate velocity
cPosition += fMovement * velocity;
// Update the view matrix based on the new information
updateViewMatrix();
}
private:
// Disable copying and assignment
Camera(Camera const &) = delete;
Camera & operator =(Camera const &) = delete;
// Private member function
/* Update the view matrix based on the current information */
void updateViewMatrix()
{
// Adjust cursor movement using the specified sensitivity
fPitch *= cMouseSensitivity;
fYaw *= cMouseSensitivity;
// Create quaternions given the current pitch and yaw
glm::quat qPitch = glm::quat(glm::vec3(fPitch, 0.0f, 0.0f));
glm::quat qYaw = glm::quat(glm::vec3(0.0f, fYaw, 0.0f));
// Reset pitch and yaw values for the current rotation
fPitch = 0.0f;
fYaw = 0.0f;
// Update camera quaternion and normalise
cQuaternion = qYaw * qPitch * cQuaternion;
cQuaternion = glm::normalize(cQuaternion);
// Build rotation matrix using the camera quaternion
glm::mat4 matRotation = glm::mat4_cast(cQuaternion);
// Build translation matrix
glm::mat4 matTranslate = glm::translate(glm::mat4(1.0f), -cPosition);
// Update view matrix
matView = matRotation * matTranslate;
}
// Private member variables
// Camera quaternion and frame pitch and yaw
glm::quat cQuaternion;
GLfloat fPitch = 0.0f;
GLfloat fYaw = 0.0f;
// Camera position
glm::vec3 cPosition;
// Variables used for bookkeeping
GLboolean resetMouse = true;
GLboolean isMousePressed = false;
GLboolean keysInUse[512];
// Last cursor position
GLfloat lastXPos = 0.0f;
GLfloat lastYPos = 0.0f;
// Camera settings
GLfloat cMovementSpeed;
GLfloat cMouseSensitivity;
// View matrix
glm::mat4 matView;
};
}
#endif

31
src/utilities/glutils.cpp Normal file
View File

@ -0,0 +1,31 @@
#include <glad/glad.h>
#include <program.hpp>
#include "glutils.h"
unsigned int generateBuffer(Mesh &mesh) {
unsigned int vaoID;
glGenVertexArrays(1, &vaoID);
glBindVertexArray(vaoID);
unsigned int vertexBufferID;
glGenBuffers(1, &vertexBufferID);
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);
glBufferData(GL_ARRAY_BUFFER, mesh.vertices.size() * sizeof(glm::vec3), mesh.vertices.data(), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);
glEnableVertexAttribArray(0);
unsigned int normalBufferID;
glGenBuffers(1, &normalBufferID);
glBindBuffer(GL_ARRAY_BUFFER, normalBufferID);
glBufferData(GL_ARRAY_BUFFER, mesh.normals.size() * sizeof(glm::vec3), mesh.normals.data(), GL_STATIC_DRAW);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_TRUE, 3 * sizeof(float), 0);
glEnableVertexAttribArray(1);
unsigned int indexBufferID;
glGenBuffers(1, &indexBufferID);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferID);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, mesh.indices.size() * sizeof(unsigned int), mesh.indices.data(), GL_STATIC_DRAW);
return vaoID;
}

5
src/utilities/glutils.h Normal file
View File

@ -0,0 +1,5 @@
#pragma once
#include "mesh.h"
unsigned int generateBuffer(Mesh &mesh);

12
src/utilities/mesh.h Normal file
View File

@ -0,0 +1,12 @@
#pragma once
#include <vector>
#include <glm/glm.hpp>
struct Mesh {
std::vector<glm::vec3> vertices;
std::vector<glm::vec3> normals;
std::vector<glm::vec2> textureCoordinates;
std::vector<unsigned int> indices;
};

146
src/utilities/shader.hpp Normal file
View File

@ -0,0 +1,146 @@
#ifndef SHADER_HPP
#define SHADER_HPP
#pragma once
// System headers
#include <glad/glad.h>
// Standard headers
#include <cassert>
#include <fstream>
#include <memory>
#include <string>
namespace Gloom
{
class Shader
{
public:
Shader() { mProgram = glCreateProgram(); }
// Public member functions
void activate() { glUseProgram(mProgram); }
void deactivate() { glUseProgram(0); }
GLuint get() { return mProgram; }
void destroy() { glDeleteProgram(mProgram); }
/* Attach a shader to the current shader program */
void attach(std::string const &filename)
{
// Load GLSL Shader from source
std::ifstream fd(filename.c_str());
if (fd.fail())
{
fprintf(stderr,
"Something went wrong when attaching the Shader file at \"%s\".\n"
"The file may not exist or is currently inaccessible.\n",
filename.c_str());
return;
}
auto src = std::string(std::istreambuf_iterator<char>(fd),
(std::istreambuf_iterator<char>()));
// Create shader object
const char * source = src.c_str();
auto shader = create(filename);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
// Display errors
glGetShaderiv(shader, GL_COMPILE_STATUS, &mStatus);
if (!mStatus)
{
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &mLength);
std::unique_ptr<char[]> buffer(new char[mLength]);
glGetShaderInfoLog(shader, mLength, nullptr, buffer.get());
fprintf(stderr, "%s\n%s", filename.c_str(), buffer.get());
}
assert(mStatus);
// Attach shader and free allocated memory
glAttachShader(mProgram, shader);
glDeleteShader(shader);
}
/* Links all attached shaders together into a shader program */
void link()
{
// Link all attached shaders
glLinkProgram(mProgram);
// Display errors
glGetProgramiv(mProgram, GL_LINK_STATUS, &mStatus);
if (!mStatus)
{
glGetProgramiv(mProgram, GL_INFO_LOG_LENGTH, &mLength);
std::unique_ptr<char[]> buffer(new char[mLength]);
glGetProgramInfoLog(mProgram, mLength, nullptr, buffer.get());
fprintf(stderr, "%s\n", buffer.get());
}
assert(mStatus);
}
/* Convenience function that attaches and links a vertex and a
fragment shader in a shader program */
void makeBasicShader(std::string const &vertexFilename,
std::string const &fragmentFilename)
{
attach(vertexFilename);
attach(fragmentFilename);
link();
}
/* Used for debugging shader programs (expensive to run) */
bool isValid()
{
// Validate linked shader program
glValidateProgram(mProgram);
// Display errors
glGetProgramiv(mProgram, GL_VALIDATE_STATUS, &mStatus);
if (!mStatus)
{
glGetProgramiv(mProgram, GL_INFO_LOG_LENGTH, &mLength);
std::unique_ptr<char[]> buffer(new char[mLength]);
glGetProgramInfoLog(mProgram, mLength, nullptr, buffer.get());
fprintf(stderr, "%s\n", buffer.get());
return false;
}
return true;
}
/* Helper function for creating shaders */
GLuint create(std::string const &filename)
{
// Extract file extension and create the correct shader type
auto idx = filename.rfind(".");
auto ext = filename.substr(idx + 1);
if (ext == "comp") return glCreateShader(GL_COMPUTE_SHADER);
else if (ext == "frag") return glCreateShader(GL_FRAGMENT_SHADER);
else if (ext == "geom") return glCreateShader(GL_GEOMETRY_SHADER);
else if (ext == "tcs") return glCreateShader(GL_TESS_CONTROL_SHADER);
else if (ext == "tes") return glCreateShader(GL_TESS_EVALUATION_SHADER);
else if (ext == "vert") return glCreateShader(GL_VERTEX_SHADER);
else return false;
}
private:
// Disable copying and assignment
Shader(Shader const &) = delete;
Shader & operator =(Shader const &) = delete;
// Private member variables
GLuint mProgram;
GLint mStatus;
GLint mLength;
};
}
#endif

245
src/utilities/shapes.cpp Normal file
View File

@ -0,0 +1,245 @@
#include <iostream>
#include "shapes.h"
Mesh generateBox(float width, float height, float depth, bool flipFaces) {
// Hardcoded. Sue me.
std::vector<glm::vec3> vertices = {
{0, 0, 0},
{0, 0, depth},
{0, height, depth},
{0, 0, 0},
{0, height, depth},
{0, height, 0},
{width, 0, 0},
{width, height, 0},
{width, height, depth},
{width, 0, 0},
{width, height, depth},
{width, 0, depth},
{0, 0, 0},
{width, height, 0},
{width, 0, 0},
{0, 0, 0},
{0, height, 0},
{width, height, 0},
{0, 0, depth},
{width, 0, depth},
{width, height, depth},
{0, 0, depth},
{width, height, depth},
{0, height, depth},
{0, 0, 0},
{width, 0, 0},
{width, 0, depth},
{0, 0, 0},
{width, 0, depth},
{0, 0, depth},
{width, height, 0},
{0, height, 0},
{0, height, depth},
{width, height, 0},
{0, height, depth},
{width, height, depth}};
std::vector<glm::vec3> normals = {
{-1.0, 0.0, 0.0},
{-1.0, 0.0, 0.0},
{-1.0, 0.0, 0.0},
{-1.0, 0.0, 0.0},
{-1.0, 0.0, 0.0},
{-1.0, 0.0, 0.0},
{1.0, 0.0, 0.0},
{1.0, 0.0, 0.0},
{1.0, 0.0, 0.0},
{1.0, 0.0, 0.0},
{1.0, 0.0, 0.0},
{1.0, 0.0, 0.0},
{0.0, -1.0, 0.0},
{0.0, -1.0, 0.0},
{0.0, -1.0, 0.0},
{0.0, -1.0, 0.0},
{0.0, -1.0, 0.0},
{0.0, -1.0, 0.0},
{0.0, 1.0, 0.0},
{0.0, 1.0, 0.0},
{0.0, 1.0, 0.0},
{0.0, 1.0, 0.0},
{0.0, 1.0, 0.0},
{0.0, 1.0, 0.0},
{0.0, 0.0, -1.0},
{0.0, 0.0, -1.0},
{0.0, 0.0, -1.0},
{0.0, 0.0, -1.0},
{0.0, 0.0, -1.0},
{0.0, 0.0, -1.0},
{0.0, 0.0, 1.0},
{0.0, 0.0, 1.0},
{0.0, 0.0, 1.0},
{0.0, 0.0, 1.0},
{0.0, 0.0, 1.0},
{0.0, 0.0, 1.0},
};
std::vector<unsigned int> indices = {
0, 1, 2,
3, 4, 5,
6, 7, 8,
9, 10, 11,
12, 13, 14,
15, 16, 17,
18, 19, 20,
21, 22, 23,
24, 25, 26,
27, 28, 29,
30, 31, 32,
33, 34, 35
};
if(flipFaces) {
for(int i = 0; i < 36; i += 3) {
unsigned int temp = indices[i + 1];
indices[i + 1] = indices[i + 2];
indices[i + 2] = temp;
normals[i + 0] *= -1;
normals[i + 1] *= -1;
normals[i + 2] *= -1;
}
}
Mesh mesh;
mesh.vertices = vertices;
mesh.normals = normals;
mesh.indices = indices;
return mesh;
}
Mesh generateSphere(float sphereRadius, int slices, int layers) {
const unsigned int triangleCount = slices * layers * 2;
std::vector<glm::vec3> vertices;
std::vector<glm::vec3> normals;
std::vector<unsigned int> indices;
vertices.reserve(3 * triangleCount);
normals.reserve(3 * triangleCount);
indices.reserve(3 * triangleCount);
// Slices require us to define a full revolution worth of triangles.
// Layers only requires angle varying between the bottom and the top (a layer only covers half a circle worth of angles)
const float degreesPerLayer = 180.0 / (float) layers;
const float degreesPerSlice = 360.0 / (float) slices;
unsigned int i = 0;
// Constructing the sphere one layer at a time
for (int layer = 0; layer < layers; layer++) {
int nextLayer = layer + 1;
// Angles between the vector pointing to any point on a particular layer and the negative z-axis
float currentAngleZDegrees = degreesPerLayer * layer;
float nextAngleZDegrees = degreesPerLayer * nextLayer;
// All coordinates within a single layer share z-coordinates.
// So we can calculate those of the current and subsequent layer here.
float currentZ = -cos(glm::radians(currentAngleZDegrees));
float nextZ = -cos(glm::radians(nextAngleZDegrees));
// The row of vertices forms a circle around the vertical diagonal (z-axis) of the sphere.
// These radii are also constant for an entire layer, so we can precalculate them.
float radius = sin(glm::radians(currentAngleZDegrees));
float nextRadius = sin(glm::radians(nextAngleZDegrees));
// Now we can move on to constructing individual slices within a layer
for (int slice = 0; slice < slices; slice++) {
// The direction of the start and the end of the slice in the xy-plane
float currentSliceAngleDegrees = slice * degreesPerSlice;
float nextSliceAngleDegrees = (slice + 1) * degreesPerSlice;
// Determining the direction vector for both the start and end of the slice
float currentDirectionX = cos(glm::radians(currentSliceAngleDegrees));
float currentDirectionY = sin(glm::radians(currentSliceAngleDegrees));
float nextDirectionX = cos(glm::radians(nextSliceAngleDegrees));
float nextDirectionY = sin(glm::radians(nextSliceAngleDegrees));
vertices.emplace_back(sphereRadius * radius * currentDirectionX,
sphereRadius * radius * currentDirectionY,
sphereRadius * currentZ);
vertices.emplace_back(sphereRadius * radius * nextDirectionX,
sphereRadius * radius * nextDirectionY,
sphereRadius * currentZ);
vertices.emplace_back(sphereRadius * nextRadius * nextDirectionX,
sphereRadius * nextRadius * nextDirectionY,
sphereRadius * nextZ);
vertices.emplace_back(sphereRadius * radius * currentDirectionX,
sphereRadius * radius * currentDirectionY,
sphereRadius * currentZ);
vertices.emplace_back(sphereRadius * nextRadius * nextDirectionX,
sphereRadius * nextRadius * nextDirectionY,
sphereRadius * nextZ);
vertices.emplace_back(sphereRadius * nextRadius * currentDirectionX,
sphereRadius * nextRadius * currentDirectionY,
sphereRadius * nextZ);
normals.emplace_back(radius * currentDirectionX,
radius * currentDirectionY,
currentZ);
normals.emplace_back(radius * nextDirectionX,
radius * nextDirectionY,
currentZ);
normals.emplace_back(nextRadius * nextDirectionX,
nextRadius * nextDirectionY,
nextZ);
normals.emplace_back(radius * currentDirectionX,
radius * currentDirectionY,
currentZ);
normals.emplace_back(nextRadius * nextDirectionX,
nextRadius * nextDirectionY,
nextZ);
normals.emplace_back(nextRadius * currentDirectionX,
nextRadius * currentDirectionY,
nextZ);
indices.emplace_back(i + 0);
indices.emplace_back(i + 1);
indices.emplace_back(i + 2);
indices.emplace_back(i + 3);
indices.emplace_back(i + 4);
indices.emplace_back(i + 5);
i += 6;
}
}
Mesh mesh;
mesh.vertices = vertices;
mesh.normals = normals;
mesh.indices = indices;
return mesh;
}

5
src/utilities/shapes.h Normal file
View File

@ -0,0 +1,5 @@
#pragma once
#include "mesh.h"
Mesh generateBox(float width, float height, float depth, bool flipFaces = false);
Mesh generateSphere(float radius, int slices, int layers);

View File

@ -0,0 +1,23 @@
#include <chrono>
#include "timeutils.h"
// In order to be able to calculate when the getTimeDeltaSeconds() function was last called, we need to know the point in time when that happened. This requires us to keep hold of that point in time.
// We initialise this value to the time at the start of the program.
static std::chrono::steady_clock::time_point _previousTimePoint = std::chrono::steady_clock::now();
// Calculates the elapsed time since the previous time this function was called.
double getTimeDeltaSeconds() {
// Determine the current time
std::chrono::steady_clock::time_point currentTime = std::chrono::steady_clock::now();
// Calculate the number of nanoseconds that elapsed since the previous call to this function
long long timeDelta = std::chrono::duration_cast<std::chrono::nanoseconds>(currentTime - _previousTimePoint).count();
// Convert the time delta in nanoseconds to seconds
double timeDeltaSeconds = (double)timeDelta / 1000000000.0;
// Store the previously measured current time
_previousTimePoint = currentTime;
// Return the calculated time delta in seconds
return timeDeltaSeconds;
}

View File

@ -0,0 +1,3 @@
#pragma once
double getTimeDeltaSeconds();

19
src/utilities/window.hpp Normal file
View File

@ -0,0 +1,19 @@
#pragma once
// System Headers
#include <glad/glad.h>
// Standard headers
#include <string>
// Constants
const int windowWidth = 1366;
const int windowHeight = 768;
const std::string windowTitle = "Glowbox";
const GLint windowResizable = GL_TRUE;
const int windowSamples = 4;
struct CommandLineOptions {
bool enableMusic;
bool enableAutoplay;
};