Files
TDT4230/src/gamelogic.cpp

358 lines
13 KiB
C++

#include <chrono>
#include <GLFW/glfw3.h>
#include <glad/glad.h>
#include <SFML/Audio/SoundBuffer.hpp>
#include <SFML/Audio/Sound.hpp>
#include <glm/vec3.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/transform.hpp>
#include <fmt/format.h>
#include <iostream>
#include <string>
#include <vector>
#include <utilities/shader.hpp>
#include <utilities/timeutils.h>
#include <utilities/mesh.h>
#include <utilities/shapes.h>
#include <utilities/glutils.h>
#include <utilities/imageLoader.hpp>
#include <utilities/glfont.h>
enum KeyFrameAction { BOTTOM, TOP };
#include "gamelogic.h"
#include "sceneGraph.hpp"
#include <timestamps.h>
struct LightSource {
glm::vec3 position;
glm::vec3 color;
};
const glm::vec3 boxDimensions(180, 90, 90);
const glm::vec3 padDimensions(30, 3, 40);
const double ballRadius = 3.0;
// modify to start music further into the track (seconds)
const float debugStartTime = 0;
SceneNode* rootNode;
SceneNode* boxNode;
SceneNode* ballNode;
SceneNode* padNode;
std::vector<SceneNode*> lightNodes;
std::vector<LightSource> lights;
glm::vec3 cameraPosition;
glm::vec3 ballPosition(0, ballRadius + padDimensions.y, boxDimensions.z / 2);
glm::vec3 ballDirection(1, 1, 0.2f);
double padPositionX = 0;
double padPositionZ = 0;
unsigned int currentKeyFrame = 0;
unsigned int previousKeyFrame = 0;
bool hasStarted = false;
bool hasLost = false;
bool jumpedToNextFrame = false;
bool isPaused = false;
bool mouseLeftPressed = false;
bool mouseLeftReleased = false;
bool mouseRightPressed = false;
bool mouseRightReleased = false;
double totalElapsedTime = debugStartTime;
double gameElapsedTime = debugStartTime;
sf::SoundBuffer* buffer;
sf::Sound* sound;
Gloom::Shader* shader;
CommandLineOptions options;
double mouseSensitivity = 1.0;
double lastMouseX = windowWidth / 2;
double lastMouseY = windowHeight / 2;
void mouseCallback(GLFWwindow* window, double x, double y) {
int w, h;
glfwGetWindowSize(window, &w, &h);
glViewport(0, 0, w, h);
double dx = x - lastMouseX;
double dy = y - lastMouseY;
padPositionX = glm::clamp(padPositionX - mouseSensitivity * dx / w, 0.0, 1.0);
padPositionZ = glm::clamp(padPositionZ - mouseSensitivity * dy / h, 0.0, 1.0);
glfwSetCursorPos(window, w / 2, h / 2);
}
void initGame(GLFWwindow* window, CommandLineOptions gameOptions) {
options = gameOptions;
buffer = new sf::SoundBuffer();
if (!buffer->loadFromFile("../res/Hall of the Mountain King.ogg")) return;
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 padMesh = cube(padDimensions, glm::vec2(30, 40), true);
Mesh boxMesh = cube(boxDimensions, glm::vec2(90), true, true); // inverted for interior view
Mesh ballMesh = generateSphere(1.0, 40, 40);
unsigned int padVAO = generateBuffer(padMesh);
unsigned int boxVAO = generateBuffer(boxMesh);
unsigned int ballVAO = generateBuffer(ballMesh);
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 = boxMesh.indices.size();
padNode->vertexArrayObjectID = padVAO;
padNode->VAOIndexCount = padMesh.indices.size();
ballNode->vertexArrayObjectID = ballVAO;
ballNode->VAOIndexCount = ballMesh.indices.size();
for (int i = 0; i < 3; i++) {
auto* light = new SceneNode();
light->nodeType = POINT_LIGHT;
light->lightIdx = i;
lightNodes.push_back(light);
}
auto* ceilingLight = lightNodes[0];
auto* padLight = lightNodes[1];
auto* ballLight = lightNodes[2];
boxNode->children.push_back(ceilingLight);
ceilingLight->position = { 0, 0, 5 };
boxNode->children.push_back(padLight);
padLight->position = { -10, 0, 5 };
boxNode->children.push_back(ballLight);
ballLight->position = { 10, 0, 5 };
getTimeDeltaSeconds(); // prime the timer
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 dt = getTimeDeltaSeconds();
const float cameraWallOffset = 30;
const float ballBottomY = boxNode->position.y - boxDimensions.y/2 + ballRadius + padDimensions.y;
const float ballTopY = boxNode->position.y + boxDimensions.y/2 - ballRadius;
const float travelY = ballTopY - ballBottomY;
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;
mouseLeftReleased = !glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) && mouseLeftPressed;
mouseLeftPressed = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1);
mouseRightReleased = !glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2) && mouseRightPressed;
mouseRightPressed = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2);
if (!hasStarted) {
if (mouseLeftPressed) {
if (options.enableMusic) {
sound = new sf::Sound();
sound->setBuffer(*buffer);
sound->setPlayingOffset(sf::seconds(debugStartTime));
sound->play();
}
totalElapsedTime = debugStartTime;
gameElapsedTime = debugStartTime;
hasStarted = true;
}
ballPosition.x = ballMinX + (1 - padPositionX) * (ballMaxX - ballMinX);
ballPosition.y = ballBottomY;
ballPosition.z = ballMinZ + (1 - padPositionZ) * (ballMaxZ + cameraWallOffset - ballMinZ);
} else {
totalElapsedTime += dt;
if (hasLost) {
if (mouseLeftReleased) {
hasLost = hasStarted = false;
currentKeyFrame = previousKeyFrame = 0;
}
} else if (isPaused) {
if (mouseRightReleased) {
isPaused = false;
if (options.enableMusic) sound->play();
}
} else {
gameElapsedTime += dt;
if (mouseRightReleased) {
isPaused = true;
if (options.enableMusic) sound->pause();
}
for (unsigned int i = currentKeyFrame; i < keyFrameTimeStamps.size(); i++) {
if (gameElapsedTime >= keyFrameTimeStamps[i]) currentKeyFrame = i;
}
jumpedToNextFrame = (currentKeyFrame != previousKeyFrame);
previousKeyFrame = currentKeyFrame;
double t0 = keyFrameTimeStamps[currentKeyFrame];
double t1 = keyFrameTimeStamps[currentKeyFrame + 1];
double frac = (gameElapsedTime - t0) / (t1 - t0);
KeyFrameAction from = keyFrameDirections[currentKeyFrame];
KeyFrameAction to = keyFrameDirections[currentKeyFrame + 1];
double ballY = ballBottomY;
if (from == BOTTOM && to == TOP) ballY = ballBottomY + travelY * frac;
else if (from == TOP && to == BOTTOM) ballY = ballBottomY + travelY * (1 - frac);
else if (from == TOP && to == TOP) ballY = ballTopY;
const float ballSpeed = 60.0f;
ballPosition.x += dt * ballSpeed * ballDirection.x;
ballPosition.y = ballY;
ballPosition.z += dt * ballSpeed * ballDirection.z;
if (ballPosition.x < ballMinX) { ballPosition.x = ballMinX; ballDirection.x *= -1; }
if (ballPosition.x > ballMaxX) { ballPosition.x = ballMaxX; ballDirection.x *= -1; }
if (ballPosition.z < ballMinZ) { ballPosition.z = ballMinZ; ballDirection.z *= -1; }
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);
}
if (jumpedToNextFrame && from == BOTTOM && to == TOP) {
double padL = boxNode->position.x - boxDimensions.x/2 + (1 - padPositionX) * (boxDimensions.x - padDimensions.x);
double padR = padL + padDimensions.x;
double padF = boxNode->position.z - boxDimensions.z/2 + (1 - padPositionZ) * (boxDimensions.z - padDimensions.z);
double padB = padF + padDimensions.z;
if (ballPosition.x < padL || ballPosition.x > padR ||
ballPosition.z < padF || ballPosition.z > padB) {
hasLost = true;
if (options.enableMusic) { sound->stop(); delete sound; }
}
}
}
}
cameraPosition = glm::vec3(0, 2, -20);
float lookRot = -0.6f / (1 + exp(-5 * (padPositionX - 0.5))) + 0.3f;
glm::mat4 view = glm::rotate(0.3f + 0.2f * float(-padPositionZ * padPositionZ), glm::vec3(1,0,0))
* glm::rotate(lookRot, glm::vec3(0,1,0))
* glm::translate(-cameraPosition);
glm::mat4 proj = glm::perspective(glm::radians(80.0f),
float(windowWidth) / float(windowHeight),
0.1f, 350.f);
glm::mat4 VP = proj * view;
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, glm::mat4(1.0f));
// update lights
lights.clear();
for (auto* node : lightNodes) {
LightSource l;
l.position = node->worldPosition;
switch (node->lightIdx) {
case 0: l.color = glm::vec3(1, 0, 0); break; // red (ceiling)
case 1: l.color = glm::vec3(0, 1, 0); break; // green (pad)
case 2: l.color = glm::vec3(0, 0, 1); break; // blue (ball)
}
lights.push_back(l);
}
}
void updateNodeTransformations(SceneNode* node, glm::mat4 vpSoFar, glm::mat4 modelSoFar) {
glm::mat4 local = 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 = vpSoFar * local; // MVP
node->modelMatrix = modelSoFar * local; // world transform
node->normalMatrix = glm::transpose(glm::inverse(glm::mat3(node->modelMatrix)));
if (node->nodeType == POINT_LIGHT) {
node->worldPosition = glm::vec3(node->modelMatrix * glm::vec4(0, 0, 0, 1));
}
for (auto* child : node->children) {
updateNodeTransformations(child, node->currentTransformationMatrix, node->modelMatrix);
}
}
void renderNode(SceneNode* node) {
glUniformMatrix4fv(3, 1, GL_FALSE, glm::value_ptr(node->currentTransformationMatrix));
glUniformMatrix4fv(4, 1, GL_FALSE, glm::value_ptr(node->modelMatrix));
glUniformMatrix3fv(5, 1, GL_FALSE, glm::value_ptr(node->normalMatrix));
glUniform3fv(shader->getUniformFromName("cameraPosition"), 1, glm::value_ptr(cameraPosition));
glUniform3fv(shader->getUniformFromName("ballPosition"), 1, glm::value_ptr(ballPosition));
for (size_t i = 0; i < lights.size(); i++) {
std::string base = "lights[" + std::to_string(i) + "]";
glUniform3fv(shader->getUniformFromName((base + ".position").c_str()), 1, glm::value_ptr(lights[i].position));
glUniform3fv(shader->getUniformFromName((base + ".color").c_str()), 1, glm::value_ptr(lights[i].color));
}
if (node->nodeType == GEOMETRY && node->vertexArrayObjectID != -1) {
glBindVertexArray(node->vertexArrayObjectID);
glDrawElements(GL_TRIANGLES, node->VAOIndexCount, GL_UNSIGNED_INT, nullptr);
}
for (auto* child : node->children) {
renderNode(child);
}
}
void renderFrame(GLFWwindow* window) {
int w, h;
glfwGetWindowSize(window, &w, &h);
glViewport(0, 0, w, h);
renderNode(rootNode);
}