#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gamelogic.h" #include "glm/ext/vector_float3.hpp" #include "glm/matrix.hpp" #include "sceneGraph.hpp" #define GLM_ENABLE_EXPERIMENTAL #include #include "utilities/imageLoader.hpp" #include "utilities/glfont.h" enum KeyFrameAction { BOTTOM, TOP }; #include double padPositionX = 0; double padPositionZ = 0; unsigned int currentKeyFrame = 0; unsigned int previousKeyFrame = 0; SceneNode* rootNode; SceneNode* boxNode; SceneNode* ballNode; SceneNode* padNode; std::vector lightNodes; std::vector lightCoords; glm::vec3 cameraPosition; 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, 90); const glm::vec3 padDimensions(30, 3, 40); glm::vec3 ballPosition(0, ballRadius + padDimensions.y, boxDimensions.z / 2); 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); double deltaX = x - lastMouseX; double deltaY = y - lastMouseY; 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")) { 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(); // 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); // Construct scene rootNode = createSceneNode(); boxNode = createSceneNode(); padNode = createSceneNode(); ballNode = createSceneNode(); for (int i = 0; i < 3; i++) { auto lightNode = new SceneNode(); lightNode->nodeType = POINT_LIGHT; lightNode->lightIdx = i; lightNodes.push_back(lightNode); } auto ceilingLight = lightNodes.at(0); auto padLight = lightNodes.at(1); auto ballLight = lightNodes.at(2); ceilingLight->position = { boxDimensions.x / 2, boxDimensions.y, boxDimensions.z / 2 }; rootNode->children.push_back(ceilingLight); padLight->position = { padPositionX, 50, padPositionZ }; padNode->children.push_back(padLight); ballLight->position = ballPosition; ballNode->children.push_back(ballLight); 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 << fmt::format("Initialized scene with {} SceneNodes.", totalChildren(rootNode)) << std::endl; std::cout << "Ready. Click to start!" << std::endl; } void updateFrame(GLFWwindow* window) { glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); double timeDelta = getTimeDeltaSeconds(); 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; 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); sf::Time startTime = sf::seconds(debug_startTime); sound->setPlayingOffset(startTime); sound->play(); } totalElapsedTime = debug_startTime; gameElapsedTime = debug_startTime; hasStarted = true; } ballPosition.x = ballMinX + (1 - padPositionX) * (ballMaxX - ballMinX); ballPosition.y = ballBottomY; ballPosition.z = ballMinZ + (1 - padPositionZ) * ((ballMaxZ+cameraWallOffset) - ballMinZ); } else { totalElapsedTime += timeDelta; if(hasLost) { 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 (gameElapsedTime < 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 = gameElapsedTime - frameStart; double frameDuration = frameEnd - frameStart; double fractionFrameComplete = elapsedTimeInFrame / frameDuration; double ballYCoord; 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) { ballYCoord = ballBottomY + BallVerticalTravelDistance; } else if (currentDestination == BOTTOM) { ballYCoord = ballBottomY + BallVerticalTravelDistance * (1 - fractionFrameComplete); } else if (currentDestination == TOP) { 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; // Make ball bounce if (ballPosition.x < ballMinX) { ballPosition.x = ballMinX; ballDirection.x *= -1; } else if (ballPosition.x > ballMaxX) { ballPosition.x = ballMaxX; ballDirection.x *= -1; } if (ballPosition.z < ballMinZ) { ballPosition.z = ballMinZ; ballDirection.z *= -1; } else if (ballPosition.z > ballMaxZ) { ballPosition.z = ballMaxZ; ballDirection.z *= -1; } if(options.enableAutoplay) { 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 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; 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(80.0f), float(windowWidth) / float(windowHeight), 0.1f, 350.f); 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); for (auto* light : lightNodes) { lightCoords.push_back(light->worldPosition); } } 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; node->modelMatrix = transformationMatrix; node->normalMatrix = glm::transpose(glm::inverse(glm::mat3(node->modelMatrix))); switch(node->nodeType) { case GEOMETRY: break; case POINT_LIGHT: node->worldPosition = glm::vec3(node->currentTransformationMatrix * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); 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)); glUniformMatrix4fv(4, 1, GL_FALSE, glm::value_ptr(node->modelMatrix)); glUniformMatrix4fv(5, 1, GL_FALSE, glm::value_ptr(node->normalMatrix)); GLint lightPosLoc = shader->getUniformFromName("lightPositions"); glUniform3fv(lightPosLoc, 3, glm::value_ptr(lightCoords[0])); GLint cameraPosLoc = shader->getUniformFromName("cameraPosition"); glUniform3fv(cameraPosLoc, 1, glm::value_ptr(cameraPosition)); 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); }