#include "gamelogic.h" #include "sceneGraph.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using glm::vec3; using glm::vec4; using glm::mat4; typedef unsigned int uint; enum KeyFrameAction { BOTTOM, TOP }; #include uint currentKeyFrame = 0; uint previousKeyFrame = 0; SceneNode* rootNode; SceneNode* plainNode; SceneNode* boxNode; SceneNode* hudNode; SceneNode* textNode; const uint N_LIGHTS = 1; SceneNode* lightNode[N_LIGHTS]; // These are heap allocated, because they should not be initialised at the start of the program sf::Sound* sound; sf::SoundBuffer* buffer; Gloom::Shader* default_shader; Gloom::Shader* test_shader; Gloom::Shader* plain_shader; Gloom::Shader* post_shader; vec3 cameraPosition = vec3(0, 0, 400); vec3 cameraLookAt = vec3(500, 500, 0); vec3 cameraUpward = vec3(0, 0, 1); 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 = 45; double totalElapsedTime = debug_startTime; // textures PNGImage t_charmap = loadPNGFile("../res/textures/charmap.png"); PNGImage t_cobble_diff = loadPNGFile("../res/textures/cobble_diff.png"); PNGImage t_cobble_normal = loadPNGFile("../res/textures/cobble_normal.png"); PNGImage t_plain_diff = loadPNGFile("../res/textures/plain_diff.png"); PNGImage t_plain_normal = loadPNGFile("../res/textures/plain_normal.png", true); PNGImage t_perlin = makePerlinNoisePNG(256, 256, 0.05/16); void mouseCallback(GLFWwindow* window, double x, double y) { int windowWidth, windowHeight; glfwGetWindowSize(window, &windowWidth, &windowHeight); glViewport(0, 0, windowWidth, windowHeight); float mousePositionX = x / double(windowHeight); // like the hudNode space float mousePositionY = 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); // load shaders default_shader = new Gloom::Shader(); default_shader->makeBasicShader("../res/shaders/simple.vert", "../res/shaders/simple.frag"); Mesh box = generateBox(50, 50, 50); Mesh plain = generateSegmentedPlane(1000, 1000, 100, 100, 3); Mesh hello_world = generateTextGeometryBuffer("Skjer'a bagera?", 1.3, 2); t_perlin.repeat_mirrored = true; rootNode = createSceneNode(); hudNode = createSceneNode(); // create and add lights to graph for (uint i = 0; ilightID = i; rootNode->children.push_back(lightNode[i]); } //create the scene: plainNode = createSceneNode(); plainNode->setTexture(&t_plain_diff, &t_plain_normal, &t_perlin); plainNode->setMesh(&plain); plainNode->position = {0, 0, 0}; plainNode->shinyness = 20; plainNode->displacementCoefficient = 40; rootNode->children.push_back(plainNode); boxNode = createSceneNode(); boxNode->setTexture(&t_cobble_diff, &t_cobble_normal); boxNode->setMesh(&box); boxNode->position = {500, 500, 40}; boxNode->referencePoint = {25, 25, 25}; boxNode->scale *= 2; boxNode->shinyness = 20; boxNode->displacementCoefficient = 40; rootNode->children.push_back(boxNode); lightNode[0]->position = {-600, 1400, 800}; lightNode[0]->color_emissive = vec3(0.35); lightNode[0]->color_diffuse = vec3(0.6); lightNode[0]->color_specular = vec3(0.1); lightNode[0]->attenuation = vec3(1.0, 0.0, 0.0); textNode = createSceneNode(); textNode->setTexture(&t_charmap); textNode->setMesh(&hello_world); textNode->position = vec3(-1.0, -1.0, 0.0); textNode->isIlluminated = false; textNode->isInverted = true; hudNode->children.push_back(textNode); getTimeDeltaSeconds(); std::cout << "Ready. Click to start!" << std::endl; } void updateNodeTransformations(SceneNode* node, mat4 transformationThusFar, mat4 const& V, mat4 const& P) { mat4 transformationMatrix = glm::translate(mat4(1.0), node->position) * glm::translate(mat4(1.0), node->referencePoint) * glm::rotate(mat4(1.0), node->rotation.z, vec3(0,0,1)) * glm::rotate(mat4(1.0), node->rotation.y, vec3(0,1,0)) * glm::rotate(mat4(1.0), node->rotation.x, vec3(1,0,0)) * glm::scale(mat4(1.0), node->scale) * glm::translate(mat4(1.0), -node->referencePoint); mat4 M = transformationThusFar * transformationMatrix; node->MV = V*M; node->MVP = P*node->MV; node->MVnormal = glm::inverse(glm::transpose(node->MV)); for(SceneNode* child : node->children) { updateNodeTransformations(child, M, V, P); } // move this into the renderNode method and have it be targeted_node from the spot if (node->targeted_by != nullptr) { assert(node->targeted_by->nodeType == SPOT_LIGHT); node->targeted_by->rotation = vec3(node->MV*vec4(node->position, 1.0)); } } void updateFrame(GLFWwindow* window, int windowWidth, int windowHeight) { 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; } } 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 (uint i = currentKeyFrame; i < keyFrameTimeStamps.size(); i++) { if (totalElapsedTime < keyFrameTimeStamps.at(i)) { continue; } currentKeyFrame = i; } jumpedToNextFrame = currentKeyFrame != previousKeyFrame; previousKeyFrame = currentKeyFrame; } } mat4 projection = glm::perspective( glm::radians(45.0f), // fovy float(windowWidth) / float(windowHeight), // aspect 0.1f, 5000.f // near, far ); mat4 cameraTransform = glm::lookAt(cameraPosition, cameraLookAt, cameraUpward); updateNodeTransformations(rootNode, mat4(1.0), cameraTransform, projection); // We orthographic now, bitches! // set orthographic VP cameraTransform = mat4(1.0); projection = glm::ortho(-float(windowWidth) / float(windowHeight), float(windowWidth) / float(windowHeight), -1.0f, 1.0f); updateNodeTransformations(hudNode, mat4(1.0), cameraTransform, projection); // update positions of nodes (like the car) plainNode->uvOffset.x += timeDelta*0.5; plainNode->uvOffset.y -= timeDelta*0.5; boxNode->rotation.z += timeDelta; lightNode[1]->rotation.z -= timeDelta; } void renderNode(SceneNode* node, Gloom::Shader* parent_shader = default_shader) { struct Light { // lights as stored in the shader // coordinates in MV space vec3 position; // MV vec3 attenuation; vec3 color_emissive; vec3 color_diffuse; vec3 color_specular; bool is_spot; vec3 spot_target; // MV float spot_cuttof_angle; void push_to_shader(Gloom::Shader* shader, uint id) { #define L(x) shader->location("light[" + std::to_string(id) + "]." #x) #define V(x) glUniform3fv(L(x), 1, glm::value_ptr(x)) glUniform1i (L(is_spot) , is_spot); glUniform1f (L(spot_cuttof_angle), spot_cuttof_angle); V(position); V(spot_target); V(attenuation); V(color_emissive); V(color_diffuse); V(color_specular); #undef v #undef l } }; static Light lights[N_LIGHTS]; static Gloom::Shader* s = nullptr; // The currently active shader // activate the correct shader Gloom::Shader* node_shader = (node->shader != nullptr) ? node->shader : parent_shader; if (s != node_shader) { s = node_shader; s->activate(); uint i = 0; for (Light l : lights) l.push_to_shader(s, i++); } switch(node->nodeType) { case GEOMETRY: if(node->vertexArrayObjectID != -1) { // load uniforms glUniformMatrix4fv(s->location("MVP") , 1, GL_FALSE, glm::value_ptr(node->MVP)); glUniformMatrix4fv(s->location("MV") , 1, GL_FALSE, glm::value_ptr(node->MV)); glUniformMatrix4fv(s->location("MVnormal"), 1, GL_FALSE, glm::value_ptr(node->MVnormal)); glUniform2fv(s->location("uvOffset") , 1, glm::value_ptr(node->uvOffset)); glUniform1f( s->location("shinyness"), node->shinyness); glUniform1f( s->location("displacementCoefficient"), node->displacementCoefficient); glUniform1ui(s->location("isTextured"), node->isTextured); glUniform1ui(s->location("isNormalMapped"), node->isNormalMapped); glUniform1ui(s->location("isDisplacementMapped"), node->isDisplacementMapped); glUniform1ui(s->location("isIlluminated"), node->isIlluminated); glUniform1ui(s->location("isInverted"), node->isInverted); if (node->isTextured) glBindTextureUnit(0, node->diffuseTextureID); if (node->isNormalMapped) glBindTextureUnit(1, node->normalTextureID); if (node->isDisplacementMapped) glBindTextureUnit(2, node->displacementTextureID); glBindVertexArray(node->vertexArrayObjectID); glDrawElements(GL_TRIANGLES, node->VAOIndexCount, GL_UNSIGNED_INT, nullptr); } break; case SPOT_LIGHT: case POINT_LIGHT: { uint id = node->lightID; lights[id].position = vec3(node->MV * vec4(1.0)); lights[id].is_spot = node->nodeType == SPOT_LIGHT; lights[id].spot_target = node->rotation; // already MV space, todo: change this lights[id].spot_cuttof_angle = glm::sin(node->spot_cuttof_angle); lights[id].attenuation = node->attenuation; lights[id].color_emissive = node->color_emissive; lights[id].color_diffuse = node->color_diffuse; lights[id].color_specular = node->color_specular; lights[id].push_to_shader(s, id); break; } default: break; } for(SceneNode* child : node->children) { renderNode(child, node_shader); } } void renderFrame(GLFWwindow* window, int windowWidth, int windowHeight) { glViewport(0, 0, windowWidth, windowHeight); renderNode(rootNode); renderNode(hudNode); }