#include #include #include #include #include #include #include #include #include "renderer.h" #include "platform.h" #include "world.h" #include "mesh.h" #include "light.h" #include "camera.h" #include "cinematic.h" #include "json.hpp" SDL_Window* window = nullptr; Renderer* renderer = nullptr; std::vector platform::getRequiredExtensions() { uint32_t count = 0; SDL_Vulkan_GetInstanceExtensions(window, &count, nullptr); std::vector names(count); SDL_Vulkan_GetInstanceExtensions(window, &count, names.data()); return names; } uint32_t platform::getTime() { return SDL_GetTicks(); } int windowX = SDL_WINDOWPOS_CENTERED; int windowY = SDL_WINDOWPOS_CENTERED; int windowWidth = 640; int windowHeight = 480; int windowFullscreen = 0; int toInt(const std::string &str) { std::stringstream ss(str); int num; if((ss >> num).fail()) return -1; return num; } void readConfig() { std::ifstream file("config.txt"); if(!file) return; std::string line; while(std::getline(file, line)) { if(line.find('=') != std::string::npos) { std::string key, value; key = line.substr(0, line.find('=')); value = line.substr(line.find('=') + 1, line.length()); if(key == "x") { windowX = toInt(value); } else if(key == "y") { windowY = toInt(value); } else if(key == "width") { windowWidth = toInt(value); } else if(key == "height") { windowHeight = toInt(value); } else if(key == "fullscreen") { windowFullscreen = toInt(value); } } } } void writeConfig() { std::ofstream file("config.txt"); if(!file) return; int x, y; SDL_GetWindowPosition(window, &x, &y); file << "x=" << x << "\n"; file << "y=" << y << "\n"; int w, h; SDL_GetWindowSize(window, &w, &h); file << "width=" << w << "\n"; file << "height=" << h << "\n"; file << "fullscreen=" << windowFullscreen << "\n"; } Mesh* loadMesh(const char* path) { Assimp::Importer importer; const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate); aiMesh* m = scene->mMeshes[0]; Mesh* mesh = new Mesh(); for(unsigned int i = 0; i < m->mNumVertices; i++) { Vertex vertex; vertex.position = glm::vec3(m->mVertices[i].x, m->mVertices[i].y, m->mVertices[i].z); vertex.normal = glm::vec3(m->mNormals[i].x, m->mNormals[i].y, m->mNormals[i].z); mesh->vertices.push_back(vertex); } for(unsigned int i = 0; i < m->mNumFaces; i++) { aiFace face = m->mFaces[i]; for(unsigned int j = 0; j < face.mNumIndices; j++) mesh->indices.push_back(face.mIndices[j]); } renderer->fillMeshBuffers(mesh); return mesh; } std::vector tokenize(const std::string& str) { size_t lastPos = str.find_first_not_of(',', 0); size_t pos = str.find_first_of(',', lastPos); std::vector tokens; while(pos != std::string::npos || lastPos != std::string::npos) { tokens.push_back(str.substr(lastPos, pos - lastPos)); lastPos = str.find_first_not_of(',', pos); pos = str.find_first_of(',', lastPos); } return tokens; } Cinematic* loadCinematic(const char* path) { std::ifstream file(path); if(!file) return nullptr; nlohmann::json json; file >> json; Cinematic* cinematic = new Cinematic(); for(auto shotObject : json["shots"]) { Shot* shot = new Shot(); shot->start = shotObject["start"]; shot->end = shotObject["end"]; for(auto meshObject : shotObject["meshes"]) { Mesh* mesh = loadMesh(meshObject["path"].get().c_str()); mesh->name = meshObject["name"]; shot->meshes.push_back(mesh); } for(auto animationObject : shotObject["animations"]) { Animation* animation = new Animation(); for(auto mesh : shot->meshes) { if(mesh->name == animationObject["target"]) animation->target = mesh; } const auto property = animationObject["property"]; if(property == "position") animation->property = AnimationProperty::Position; for(auto keyframeObject : animationObject["keyframes"]) { Keyframe keyframe; keyframe.time = keyframeObject["time"]; auto tokens = tokenize(keyframeObject["value"]); keyframe.value[0] = atof(tokens[0].c_str()); keyframe.value[1] = atof(tokens[1].c_str()); keyframe.value[2] = atof(tokens[2].c_str()); animation->keyframes.push_back(keyframe); } shot->animations.push_back(animation); } cinematic->shots.push_back(shot); } return cinematic; } Cinematic* cinematic = nullptr; int main(int argc, char* argv[]) { readConfig(); bool cinematicMode = false; if(argc > 2 && strcmp(argv[1], "--cinematic") == 0) cinematicMode = true; window = SDL_CreateWindow("Graph", windowX, windowY, windowWidth, windowHeight, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE); if(window == nullptr) return -1; SDL_SetWindowFullscreen(window, windowFullscreen == 1 ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); renderer = new Renderer(); VkSurfaceKHR surface = nullptr; SDL_Vulkan_CreateSurface(window, renderer->getInstance(), &surface); RenderTarget* target = renderer->createSurfaceRenderTarget(surface); World world; auto light = new Light(); light->position.y = 5; world.lights.push_back(light); Camera camera; camera.position.z = 3; if(cinematicMode) cinematic = loadCinematic(argv[2]); else world.meshes.push_back(loadMesh("data/suzanne.obj")); float currentTime = 0.0f, lastTime = 0.0f; Shot* currentShot = nullptr; bool running = true; while(running) { SDL_Event event = {}; while(SDL_PollEvent(&event) > 0) { if(event.type == SDL_QUIT) running = false; if(event.type == SDL_WINDOWEVENT) { if(event.window.event == SDL_WINDOWEVENT_RESIZED) target = renderer->createSurfaceRenderTarget(surface, target); } if(event.type == SDL_KEYDOWN && event.key.keysym.scancode == SDL_SCANCODE_F11) { if(windowFullscreen == 1) { SDL_SetWindowFullscreen(window, 0); windowFullscreen = 0; } else { SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP); windowFullscreen = 1; } target = renderer->createSurfaceRenderTarget(surface, target); } if(event.type == SDL_KEYDOWN && event.key.keysym.scancode == SDL_SCANCODE_F12) { renderer->takeScreenshot("screenshot.ppm", target); } } if(cinematicMode) { float endTime = 0.0f; for(auto shot : cinematic->shots) { if(shot->end > endTime) endTime = shot->end; if(currentTime >= shot->start && currentTime < shot->end && currentShot != shot) { if(currentShot != nullptr) { world.meshes.clear(); } for(auto mesh : shot->meshes) world.meshes.push_back(mesh); currentShot = shot; } } // we have reached the end of the cinematic if(currentTime >= endTime) { currentShot = nullptr; world.meshes.clear(); } if(currentShot != nullptr) { for(auto animation : currentShot->animations) { unsigned int frameIndex = 0; for(size_t i = 0; i < animation->keyframes.size(); i++) { if(currentTime < animation->keyframes[i + 1].time) { frameIndex = i; break; } } const auto currentFrame = animation->keyframes[frameIndex]; const auto nextFrame = animation->keyframes[(frameIndex + 1) % animation->keyframes.size()]; const float delta = (currentTime - currentFrame.time) / (nextFrame.time - currentFrame.time); glm::vec3 pos = currentFrame.value + delta * (nextFrame.value - currentFrame.value); if(animation->target != nullptr) animation->target->position = pos; else camera.position = pos; } } } renderer->render(world, camera, target); if(cinematicMode) { currentTime += ((SDL_GetTicks() / 1000.0f) - lastTime); lastTime = currentTime; static int frameNum = 0; std::string screenshotName = "frame" + std::to_string(frameNum) + ".ppm"; frameNum++; renderer->takeScreenshot(screenshotName.c_str(), target); } } if(cinematic == nullptr) { renderer->destroyMeshBuffers(world.meshes[0]); delete world.meshes[0]; } delete light; renderer->destroyRenderTarget(target); vkDestroySurfaceKHR(renderer->getInstance(), surface, nullptr); delete renderer; writeConfig(); SDL_DestroyWindow(window); return 0; }