From fca107c75b18ab6dd52022126c62ed7ee7b96f5c Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 31 Jan 2020 17:03:40 +0100 Subject: [PATCH] Improved assignments for 2020 --- .gitignore | 6 + .gitmodules | 3 + CMakeLists.txt | 7 + lib/fmt | 1 + lib/genglad_LINUX_MAC.sh | 3 + lib/genglad_WINDOWS.ps1 | 3 + res/shaders/simple.frag | 8 +- res/shaders/simple.vert | 3 + src/gamelogic.cpp | 299 ++++++++++++++++++++++---------------- src/gamelogic.h | 3 +- src/program.cpp | 4 +- src/sceneGraph.cpp | 8 + src/sceneGraph.hpp | 9 +- src/utilities/glutils.cpp | 31 ++-- src/utilities/shader.hpp | 6 + src/utilities/shapes.cpp | 262 ++++++++++----------------------- src/utilities/shapes.h | 1 + src/utilities/window.hpp | 2 +- 18 files changed, 321 insertions(+), 338 deletions(-) create mode 160000 lib/fmt create mode 100644 lib/genglad_LINUX_MAC.sh create mode 100644 lib/genglad_WINDOWS.ps1 diff --git a/.gitignore b/.gitignore index 259148f..5e84eff 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,9 @@ *.exe *.out *.app + +# Build directory +/build/* +!/build/.emptydirectory + +/.vscode/* \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 8233d4f..a14f671 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,3 +28,6 @@ [submodule "lib/lodepng"] path = lib/lodepng url = https://github.com/lvandeve/lodepng +[submodule "lib/fmt"] + path = lib/fmt + url = https://github.com/fmtlib/fmt.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a20a4a..d6b4fdb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,11 @@ option (SFML_BUILD_WINDOW OFF) option (SFML_BUILD_NETWORK OFF) add_subdirectory(lib/SFML) +# +# Add FMT +# +add_subdirectory (lib/fmt) + # # GLAD # @@ -75,6 +80,7 @@ include_directories (src/ lib/arrrgh/ lib/SFML/include/) + # # Add files # @@ -114,5 +120,6 @@ add_executable (${PROJECT_NAME} ${PROJECT_SOURCES} ${PROJECT_HEADERS} target_link_libraries (${PROJECT_NAME} glfw sfml-audio + fmt::fmt ${GLFW_LIBRARIES} ${GLAD_LIBRARIES}) diff --git a/lib/fmt b/lib/fmt new file mode 160000 index 0000000..b55ea58 --- /dev/null +++ b/lib/fmt @@ -0,0 +1 @@ +Subproject commit b55ea587053a01e242c8a83d84fa81e0acf7d973 diff --git a/lib/genglad_LINUX_MAC.sh b/lib/genglad_LINUX_MAC.sh new file mode 100644 index 0000000..2f9d74a --- /dev/null +++ b/lib/genglad_LINUX_MAC.sh @@ -0,0 +1,3 @@ +!#/bin/bash +cd glad +python3 -m glad --profile core --out-path . --generator c --spec gl \ No newline at end of file diff --git a/lib/genglad_WINDOWS.ps1 b/lib/genglad_WINDOWS.ps1 new file mode 100644 index 0000000..6f4a3ad --- /dev/null +++ b/lib/genglad_WINDOWS.ps1 @@ -0,0 +1,3 @@ +cd glad +python -m glad --profile core --out-path . --generator c --spec gl +pause \ No newline at end of file diff --git a/res/shaders/simple.frag b/res/shaders/simple.frag index 8eed273..ada7191 100644 --- a/res/shaders/simple.frag +++ b/res/shaders/simple.frag @@ -1,12 +1,14 @@ #version 430 core in layout(location = 0) vec3 normal; +in layout(location = 1) vec2 textureCoordinates; out vec4 color; +float rand(vec2 co) { return fract(sin(dot(co.xy, vec2(12.9898,78.233))) * 43758.5453); } +float dither(vec2 uv) { return (rand(uv)*2.0-1.0) / 256.0; } + 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); -} +} \ No newline at end of file diff --git a/res/shaders/simple.vert b/res/shaders/simple.vert index 1d2cbcb..b38c72d 100644 --- a/res/shaders/simple.vert +++ b/res/shaders/simple.vert @@ -2,13 +2,16 @@ in layout(location = 0) vec3 position; in layout(location = 1) vec3 normal_in; +in layout(location = 2) vec2 textureCoordinates_in; uniform layout(location = 3) mat4 MVP; out layout(location = 0) vec3 normal_out; +out layout(location = 1) vec2 textureCoordinates_out; void main() { normal_out = normal_in; + textureCoordinates_out = textureCoordinates_in; gl_Position = MVP * vec4(position, 1.0f); } diff --git a/src/gamelogic.cpp b/src/gamelogic.cpp index bef19ac..3153853 100644 --- a/src/gamelogic.cpp +++ b/src/gamelogic.cpp @@ -12,9 +12,11 @@ #include #include #include +#include #include "gamelogic.h" #include "sceneGraph.hpp" - +#define GLM_ENABLE_EXPERIMENTAL +#include enum KeyFrameAction { BOTTOM, TOP @@ -23,7 +25,7 @@ enum KeyFrameAction { #include double padPositionX = 0; -double padPositionY = 0; +double padPositionZ = 0; unsigned int currentKeyFrame = 0; unsigned int previousKeyFrame = 0; @@ -40,50 +42,57 @@ sf::SoundBuffer* buffer; Gloom::Shader* shader; sf::Sound* sound; -const glm::vec3 boxDimensions(180, 90, 50); +const glm::vec3 boxDimensions(180, 90, 90); 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; +glm::vec3 ballDirection(1, 1, 0.2f); CommandLineOptions options; bool hasStarted = false; bool hasLost = false; bool jumpedToNextFrame = false; +bool isPaused = false; + +bool mouseLeftPressed = false; +bool mouseLeftReleased = false; +bool mouseRightPressed = false; +bool mouseRightReleased = 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; +double gameElapsedTime = debug_startTime; - - +double mouseSensitivity = 1.0; +double lastMouseX = windowWidth / 2; +double lastMouseY = windowHeight / 2; 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); + double deltaX = x - lastMouseX; + double deltaY = y - lastMouseY; - 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); - } + padPositionX -= mouseSensitivity * deltaX / windowWidth; + padPositionZ -= mouseSensitivity * deltaY / windowHeight; + + if (padPositionX > 1) padPositionX = 1; + if (padPositionX < 0) padPositionX = 0; + if (padPositionZ > 1) padPositionZ = 1; + if (padPositionZ < 0) padPositionZ = 0; + + glfwSetCursorPos(window, windowWidth / 2, windowHeight / 2); } +//// A few lines to help you if you've never used c++ structs +// struct LightSource { +// bool a_placeholder_value; +// }; +// LightSource lightSources[/*Put number of light sources you want here*/]; + void initGame(GLFWwindow* window, CommandLineOptions gameOptions) { buffer = new sf::SoundBuffer(); if (!buffer->loadFromFile("../res/Hall of the Mountain King.ogg")) { @@ -99,17 +108,20 @@ void initGame(GLFWwindow* window, CommandLineOptions gameOptions) { 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); + // Create meshes + Mesh pad = cube(padDimensions, glm::vec2(30, 40), true); + Mesh box = cube(boxDimensions, glm::vec2(90), true, true); Mesh sphere = generateSphere(1.0, 40, 40); + // Fill buffers unsigned int ballVAO = generateBuffer(sphere); - unsigned int boxVAO = generateBuffer(box); - unsigned int padVAO = generateBuffer(pad); + unsigned int boxVAO = generateBuffer(box); + unsigned int padVAO = generateBuffer(pad); + // Construct scene rootNode = createSceneNode(); - boxNode = createSceneNode(); - padNode = createSceneNode(); + boxNode = createSceneNode(); + padNode = createSceneNode(); ballNode = createSceneNode(); rootNode->children.push_back(boxNode); @@ -125,49 +137,51 @@ void initGame(GLFWwindow* window, CommandLineOptions gameOptions) { ballNode->vertexArrayObjectID = ballVAO; ballNode->VAOIndexCount = sphere.indices.size(); + + + + + getTimeDeltaSeconds(); + std::cout << fmt::format("Initialized scene with {} SceneNodes.", totalChildren(rootNode)) << std::endl; + 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) { + const float ballBottomY = boxNode->position.y - (boxDimensions.y/2) + ballRadius + padDimensions.y; + const float ballTopY = boxNode->position.y + (boxDimensions.y/2) - ballRadius; + const float BallVerticalTravelDistance = ballTopY - ballBottomY; - if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1)) { + const float cameraWallOffset = 30; // Arbitrary addition to prevent ball from going too much into camera + + const float ballMinX = boxNode->position.x - (boxDimensions.x/2) + ballRadius; + const float ballMaxX = boxNode->position.x + (boxDimensions.x/2) - ballRadius; + const float ballMinZ = boxNode->position.z - (boxDimensions.z/2) + ballRadius; + const float ballMaxZ = boxNode->position.z + (boxDimensions.z/2) - ballRadius - cameraWallOffset; + + if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1)) { + mouseLeftPressed = true; + mouseLeftReleased = false; + } else { + mouseLeftReleased = mouseLeftPressed; + mouseLeftPressed = false; + } + if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2)) { + mouseRightPressed = true; + mouseRightReleased = false; + } else { + mouseRightReleased = mouseRightPressed; + mouseRightPressed = false; + } + + if(!hasStarted) { + if (mouseLeftPressed) { if (options.enableMusic) { sound = new sf::Sound(); sound->setBuffer(*buffer); @@ -176,26 +190,40 @@ void updateFrame(GLFWwindow* window) { sound->play(); } totalElapsedTime = debug_startTime; + gameElapsedTime = debug_startTime; hasStarted = true; } - ballPosition.x = (1 - padPositionX) * (boxDimensions.x - padDimensions.x) + padDimensions.x / 2.0; - ballPosition.y = ballRadius + padDimensions.y; + ballPosition.x = ballMinX + (1 - padPositionX) * (ballMaxX - ballMinX); + ballPosition.y = ballBottomY; + ballPosition.z = ballMinZ + (1 - padPositionZ) * ((ballMaxZ+cameraWallOffset) - ballMinZ); } 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; + if (mouseLeftReleased) { + hasLost = false; + hasStarted = false; + currentKeyFrame = 0; + previousKeyFrame = 0; + } + } else if (isPaused) { + if (mouseRightReleased) { + isPaused = false; + if (options.enableMusic) { + sound->play(); + } } } else { + gameElapsedTime += timeDelta; + if (mouseRightReleased) { + isPaused = true; + if (options.enableMusic) { + sound->pause(); + } + } + // Get the timing for the beat of the song for (unsigned int i = currentKeyFrame; i < keyFrameTimeStamps.size(); i++) { - if (totalElapsedTime < keyFrameTimeStamps.at(i)) { + if (gameElapsedTime < keyFrameTimeStamps.at(i)) { continue; } currentKeyFrame = i; @@ -207,17 +235,16 @@ void updateFrame(GLFWwindow* window) { double frameStart = keyFrameTimeStamps.at(currentKeyFrame); double frameEnd = keyFrameTimeStamps.at(currentKeyFrame + 1); // Assumes last keyframe at infinity - double elapsedTimeInFrame = totalElapsedTime - frameStart; + double elapsedTimeInFrame = gameElapsedTime - 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); + // Synchronize ball with music if (currentOrigin == BOTTOM && currentDestination == BOTTOM) { ballYCoord = ballBottomY; } else if (currentOrigin == TOP && currentDestination == TOP) { @@ -228,88 +255,110 @@ void updateFrame(GLFWwindow* window) { ballYCoord = ballBottomY + BallVerticalTravelDistance * fractionFrameComplete; } - + // Make ball move 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; + // Make ball bounce + if (ballPosition.x < ballMinX) { + ballPosition.x = ballMinX; ballDirection.x *= -1; - } else if (ballPosition.x - ballRadius < 0) { - ballPosition.x = ballRadius; + } else if (ballPosition.x > ballMaxX) { + ballPosition.x = ballMaxX; 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; + if (ballPosition.z < ballMinZ) { + ballPosition.z = ballMinZ; ballDirection.z *= -1; - } else if (ballPosition.z - ballRadius < 0) { - ballPosition.z = ballRadius; + } else if (ballPosition.z > ballMaxZ) { + ballPosition.z = ballMaxZ; ballDirection.z *= -1; } if(options.enableAutoplay) { - padPositionX = 1 - (ballPosition.x / (boxDimensions.x - 2 * ballRadius)); - padPositionY = 1 - (ballPosition.z / (boxDimensions.z - 2 * ballRadius)); + padPositionX = 1-(ballPosition.x - ballMinX) / (ballMaxX - ballMinX); + padPositionZ = 1-(ballPosition.z - ballMinZ) / ((ballMaxZ+cameraWallOffset) - ballMinZ); } // 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 padLeftX = boxNode->position.x - (boxDimensions.x/2) + (1 - padPositionX) * (boxDimensions.x - padDimensions.x); + double padRightX = padLeftX + padDimensions.x; + double padFrontZ = boxNode->position.z - (boxDimensions.z/2) + (1 - padPositionZ) * (boxDimensions.z - padDimensions.z); + double padBackZ = padFrontZ + padDimensions.z; - 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) { + if ( ballPosition.x < padLeftX + || ballPosition.x > padRightX + || ballPosition.z < padFrontZ + || ballPosition.z > padBackZ) { hasLost = true; if (options.enableMusic) { sound->stop(); + delete sound; } } } } } - glm::mat4 projection = glm::perspective(glm::radians(90.0f), float(windowWidth) / float(windowHeight), 0.1f, - 120.f); + glm::mat4 projection = glm::perspective(glm::radians(80.0f), float(windowWidth) / float(windowHeight), 0.1f, 350.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::vec3 cameraPosition = glm::vec3(0, 2, -20); + + // Some math to make the camera move in a nice way + float lookRotation = -0.6 / (1 + exp(-5 * (padPositionX-0.5))) + 0.3; + glm::mat4 cameraTransform = + glm::rotate(0.3f + 0.2f * float(-padPositionZ*padPositionZ), glm::vec3(1, 0, 0)) * + glm::rotate(lookRotation, glm::vec3(0, 1, 0)) * + glm::translate(-cameraPosition); glm::mat4 VP = projection * cameraTransform; + // Move and rotate various SceneNodes + boxNode->position = { 0, -10, -80 }; + + ballNode->position = ballPosition; + ballNode->scale = glm::vec3(ballRadius); + ballNode->rotation = { 0, totalElapsedTime*2, 0 }; + + padNode->position = { + boxNode->position.x - (boxDimensions.x/2) + (padDimensions.x/2) + (1 - padPositionX) * (boxDimensions.x - padDimensions.x), + boxNode->position.y - (boxDimensions.y/2) + (padDimensions.y/2), + boxNode->position.z - (boxDimensions.z/2) + (padDimensions.z/2) + (1 - padPositionZ) * (boxDimensions.z - padDimensions.z) + }; + 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 updateNodeTransformations(SceneNode* node, glm::mat4 transformationThusFar) { + glm::mat4 transformationMatrix = + glm::translate(node->position) + * glm::translate(node->referencePoint) + * glm::rotate(node->rotation.y, glm::vec3(0,1,0)) + * glm::rotate(node->rotation.x, glm::vec3(1,0,0)) + * glm::rotate(node->rotation.z, glm::vec3(0,0,1)) + * glm::scale(node->scale) + * glm::translate(-node->referencePoint); + + node->currentTransformationMatrix = transformationThusFar * transformationMatrix; + + switch(node->nodeType) { + case GEOMETRY: break; + case POINT_LIGHT: break; + case SPOT_LIGHT: break; + } + + for(SceneNode* child : node->children) { + updateNodeTransformations(child, node->currentTransformationMatrix); + } +} void renderNode(SceneNode* node) { glUniformMatrix4fv(3, 1, GL_FALSE, glm::value_ptr(node->currentTransformationMatrix)); @@ -321,12 +370,8 @@ void renderNode(SceneNode* node) { glDrawElements(GL_TRIANGLES, node->VAOIndexCount, GL_UNSIGNED_INT, nullptr); } break; - case POINT_LIGHT: - - break; - case SPOT_LIGHT: - - break; + case POINT_LIGHT: break; + case SPOT_LIGHT: break; } for(SceneNode* child : node->children) { diff --git a/src/gamelogic.h b/src/gamelogic.h index 3965eff..96afaec 100644 --- a/src/gamelogic.h +++ b/src/gamelogic.h @@ -1,8 +1,9 @@ #pragma once - #include +#include "sceneGraph.hpp" +void updateNodeTransformations(SceneNode* node, glm::mat4 transformationThusFar); void initGame(GLFWwindow* window, CommandLineOptions options); void updateFrame(GLFWwindow* window); void renderFrame(GLFWwindow* window); \ No newline at end of file diff --git a/src/program.cpp b/src/program.cpp index bb45890..54b56eb 100644 --- a/src/program.cpp +++ b/src/program.cpp @@ -24,6 +24,9 @@ void runProgram(GLFWwindow* window, CommandLineOptions options) // Configure miscellaneous OpenGL settings glEnable(GL_CULL_FACE); + // Disable built-in dithering + glDisable(GL_DITHER); + // Set default colour after clearing the colour buffer glClearColor(0.3f, 0.5f, 0.8f, 1.0f); @@ -43,7 +46,6 @@ void runProgram(GLFWwindow* window, CommandLineOptions options) - // Handle other events glfwPollEvents(); handleKeyboardInput(window); diff --git a/src/sceneGraph.cpp b/src/sceneGraph.cpp index 518d8e7..7e8a8c0 100644 --- a/src/sceneGraph.cpp +++ b/src/sceneGraph.cpp @@ -10,6 +10,14 @@ void addChild(SceneNode* parent, SceneNode* child) { parent->children.push_back(child); } +int totalChildren(SceneNode* parent) { + int count = parent->children.size(); + for (SceneNode* child : parent->children) { + count += totalChildren(child); + } + return count; +} + // Pretty prints the current values of a SceneNode instance to stdout void printNode(SceneNode* node) { printf( diff --git a/src/sceneGraph.hpp b/src/sceneGraph.hpp index 51719f3..66180a1 100644 --- a/src/sceneGraph.hpp +++ b/src/sceneGraph.hpp @@ -17,10 +17,6 @@ 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); @@ -32,6 +28,7 @@ struct SceneNode { VAOIndexCount = 0; nodeType = GEOMETRY; + } // A list of all children that belong to this node. @@ -57,11 +54,9 @@ struct SceneNode { SceneNodeType nodeType; }; -// Struct for keeping track of 2D coordinates - SceneNode* createSceneNode(); void addChild(SceneNode* parent, SceneNode* child); void printNode(SceneNode* node); - +int totalChildren(SceneNode* parent); // For more details, see SceneGraph.cpp. \ No newline at end of file diff --git a/src/utilities/glutils.cpp b/src/utilities/glutils.cpp index 26752e1..a6ce2f8 100644 --- a/src/utilities/glutils.cpp +++ b/src/utilities/glutils.cpp @@ -1,31 +1,34 @@ #include #include #include "glutils.h" +#include + +template +unsigned int generateAttribute(int id, int elementsPerEntry, std::vector data, bool normalize) { + unsigned int bufferID; + glGenBuffers(1, &bufferID); + glBindBuffer(GL_ARRAY_BUFFER, bufferID); + glBufferData(GL_ARRAY_BUFFER, data.size() * sizeof(T), data.data(), GL_STATIC_DRAW); + glVertexAttribPointer(id, elementsPerEntry, GL_FLOAT, normalize ? GL_TRUE : GL_FALSE, elementsPerEntry * sizeof(float), 0); + glEnableVertexAttribArray(id); + return bufferID; +} 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); + generateAttribute(0, 3, mesh.vertices, false); + generateAttribute(1, 3, mesh.normals, true); + if (mesh.textureCoordinates.size() > 0) { + generateAttribute(2, 2, mesh.textureCoordinates, false); + } 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; } diff --git a/src/utilities/shader.hpp b/src/utilities/shader.hpp index bd722be..69cfe22 100644 --- a/src/utilities/shader.hpp +++ b/src/utilities/shader.hpp @@ -95,6 +95,12 @@ namespace Gloom link(); } + /* Convenience function to get a uniforms ID from a string + containing its name */ + GLint getUniformFromName(std::string const &uniformName) { + return glGetUniformLocation(this->get(), uniformName.c_str()); + } + /* Used for debugging shader programs (expensive to run) */ bool isValid() diff --git a/src/utilities/shapes.cpp b/src/utilities/shapes.cpp index c0cf322..241103b 100644 --- a/src/utilities/shapes.cpp +++ b/src/utilities/shapes.cpp @@ -1,205 +1,89 @@ #include #include "shapes.h" -Mesh generateBox(float width, float height, float depth, bool flipFaces) { - // Hardcoded. Sue me. +Mesh cube(glm::vec3 scale, glm::vec2 textureScale, bool tilingTextures, bool inverted, glm::vec3 textureScale3d) { + glm::vec3 points[8]; + int indices[36]; - // Edit: well, that backfired.. + for (int y = 0; y <= 1; y++) + for (int z = 0; z <= 1; z++) + for (int x = 0; x <= 1; x++) { + points[x+y*4+z*2] = glm::vec3(x*2-1, y*2-1, z*2-1) * 0.5f * scale; + } - std::vector vertices = { - {0, 0, 0}, - {0, 0, depth}, - {0, height, depth}, - - {0, 0, 0}, - {0, height, depth}, - {0, height, 0}, - - {width, 0, 0}, - {width, height, depth}, - {width, 0, depth}, - - {width, 0, 0}, - {width, height, 0}, - {width, height, 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}, + int faces[6][4] = { + {2,3,0,1}, // Bottom + {4,5,6,7}, // Top + {7,5,3,1}, // Right + {4,6,0,2}, // Left + {5,4,1,0}, // Back + {6,7,2,3}, // Front }; - // These are technically inverted relative to the vertex coordinates. - // But for some strange reason the faces are rendered inverted. - // So to make the assignment work this is the best I can do. + scale = scale * textureScale3d; + glm::vec2 faceScale[6] = { + {-scale.x,-scale.z}, // Bottom + {-scale.x,-scale.z}, // Top + { scale.z, scale.y}, // Right + { scale.z, scale.y}, // Left + { scale.x, scale.y}, // Back + { scale.x, scale.y}, // Front + }; - std::vector 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, 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, 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}, + glm::vec3 normals[6] = { + { 0,-1, 0}, // Bottom + { 0, 1, 0}, // Top + { 1, 0, 0}, // Right + {-1, 0, 0}, // Left + { 0, 0,-1}, // Back + { 0, 0, 1}, // Front }; - float texScaleFactorX = depth / height; - float texScaleFactorY = width / depth; - float texScaleFactorZ = width / height; - - std::vector textureCoordinates = { - {0, 0}, - {texScaleFactorX, 0}, - {texScaleFactorX, 1}, - - {0, 0}, - {texScaleFactorX, 1}, - {0, 1}, - - {0, 0}, - {texScaleFactorX, 1}, - {texScaleFactorX, 0}, - - {0, 0}, - {0, 1}, - {texScaleFactorX, 1}, - - {0, 0}, - {texScaleFactorZ, 0}, - {texScaleFactorZ, 1}, - - {0, 0}, - {texScaleFactorZ, 1}, - {0, 1}, - - {0, 0}, - {texScaleFactorZ, 0}, - {texScaleFactorZ, 1}, - - {0, 0}, - {texScaleFactorZ, 1}, - {0, 1}, - - {0, 0}, - {texScaleFactorY, 0}, - {texScaleFactorY, 1}, - - {0, 0}, - {texScaleFactorY, 1}, - {0, 1}, - - {0, 0}, - {texScaleFactorY, 0}, - {texScaleFactorY, 1}, - - {0, 0}, - {texScaleFactorY, 1}, - {0, 1}, - + glm::vec2 UVs[4] = { + {0, 0}, + {0, 1}, + {1, 0}, + {1, 1}, }; + Mesh m; + for (int face = 0; face < 6; face++) { + int offset = face * 6; + indices[offset + 0] = faces[face][0]; + indices[offset + 3] = faces[face][0]; - std::vector 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 (!inverted) { + indices[offset + 1] = faces[face][3]; + indices[offset + 2] = faces[face][1]; + indices[offset + 4] = faces[face][2]; + indices[offset + 5] = faces[face][3]; + } else { + indices[offset + 1] = faces[face][1]; + indices[offset + 2] = faces[face][3]; + indices[offset + 4] = faces[face][3]; + indices[offset + 5] = faces[face][2]; + } - 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; + for (int i = 0; i < 6; i++) { + m.vertices.push_back(points[indices[offset + i]]); + m.indices.push_back(offset + i); + m.normals.push_back(normals[face] * (inverted ? -1.f : 1.f)); + } - normals[i + 0] *= -1; - normals[i + 1] *= -1; - normals[i + 2] *= -1; + glm::vec2 textureScaleFactor = tilingTextures ? (faceScale[face] / textureScale) : glm::vec2(1); + + if (!inverted) { + for (int i : {1,2,3,1,0,2}) { + m.textureCoordinates.push_back(UVs[i] * textureScaleFactor); + } + } else { + for (int i : {3,1,0,3,0,2}) { + m.textureCoordinates.push_back(UVs[i] * textureScaleFactor); + } } } - Mesh mesh; - mesh.vertices = vertices; - mesh.normals = normals; - mesh.textureCoordinates = textureCoordinates; - mesh.indices = indices; - - return mesh; + return m; } Mesh generateSphere(float sphereRadius, int slices, int layers) { @@ -208,6 +92,7 @@ Mesh generateSphere(float sphereRadius, int slices, int layers) { std::vector vertices; std::vector normals; std::vector indices; + std::vector uvs; vertices.reserve(3 * triangleCount); normals.reserve(3 * triangleCount); @@ -297,6 +182,14 @@ Mesh generateSphere(float sphereRadius, int slices, int layers) { indices.emplace_back(i + 4); indices.emplace_back(i + 5); + for (int j = 0; j < 6; j++) { + glm::vec3 vertex = vertices.at(i+j); + uvs.emplace_back( + 0.5 + (glm::atan(vertex.z, vertex.y)/(2.0*M_PI)), + 0.5 - (glm::asin(vertex.y)/M_PI) + ); + } + i += 6; } } @@ -305,5 +198,6 @@ Mesh generateSphere(float sphereRadius, int slices, int layers) { mesh.vertices = vertices; mesh.normals = normals; mesh.indices = indices; + mesh.textureCoordinates = uvs; return mesh; } diff --git a/src/utilities/shapes.h b/src/utilities/shapes.h index 5851371..6bc5351 100644 --- a/src/utilities/shapes.h +++ b/src/utilities/shapes.h @@ -1,5 +1,6 @@ #pragma once #include "mesh.h" +Mesh cube(glm::vec3 scale = glm::vec3(1), glm::vec2 textureScale = glm::vec2(1), bool tilingTextures = false, bool inverted = false, glm::vec3 textureScale3d = glm::vec3(1)); Mesh generateBox(float width, float height, float depth, bool flipFaces = false); Mesh generateSphere(float radius, int slices, int layers); \ No newline at end of file diff --git a/src/utilities/window.hpp b/src/utilities/window.hpp index dda9a47..591e753 100644 --- a/src/utilities/window.hpp +++ b/src/utilities/window.hpp @@ -10,7 +10,7 @@ const int windowWidth = 1366; const int windowHeight = 768; const std::string windowTitle = "Glowbox"; -const GLint windowResizable = GL_TRUE; +const GLint windowResizable = GL_FALSE; const int windowSamples = 4; struct CommandLineOptions {