#include #include #include #include #define GLM_ENABLE_EXPERIMENTAL #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __DEBUG #include "gl_debug.h" #endif template bool contains(const std::vector& vec, const T& val) { return std::find(vec.cbegin(), vec.cend(), val) != vec.cend(); } void print_info_log(GLuint object, bool is_program) { GLint info_len = 0; if (is_program) { glGetProgramiv(object, GL_INFO_LOG_LENGTH, &info_len); } else { glGetShaderiv(object, GL_INFO_LOG_LENGTH, &info_len); } if (info_len > 0) { std::vector buffer; buffer.resize(info_len); GLint actual_len; if (is_program) { glGetProgramInfoLog(object, buffer.size(), &actual_len, buffer.data()); } else { glGetShaderInfoLog(object, buffer.size(), &actual_len, buffer.data()); } std::cout << "[GL] Info log: " << std::endl << buffer.data() << std::endl; } } GLuint compile_shader(GLenum type, const char* file) { std::vector bytes; std::ifstream stream(file); if (!stream.is_open()) { std::cerr << "[GL] Failed to open file: " << file << std::endl; exit(1); return 0; } bytes.assign(std::istreambuf_iterator(stream), std::istreambuf_iterator()); auto id = glCreateShader(type); const auto* data = bytes.data(); auto len = static_cast(bytes.size()); glShaderSource(id, 1, &data, &len); glCompileShader(id); print_info_log(id, false); GLint status = 0; glGetShaderiv(id, GL_COMPILE_STATUS, &status); if (status != GL_TRUE) { std::cerr << "[GL] Failed to compile shader" << std::endl; exit(1); return 0; } return id; } GLuint program = 0; GLuint vao = 0; void init_gl(SDL_Window* window, SDL_GLContext context) { #ifdef __DEBUG int count = 0; glGetIntegerv(GL_NUM_EXTENSIONS, &count); std::set extensions; for(auto i = 0; i < count; i++) { const char* ext = reinterpret_cast(glGetStringi(GL_EXTENSIONS, i)); extensions.emplace(ext); } if (extensions.contains("GL_KHR_debug")) { std::cout << "[GL] Enabling debug output..." << std::endl; std::cout.flush(); glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(debug_func, nullptr); std::string msg = "Debug output test"; glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_OTHER, 494949, GL_DEBUG_SEVERITY_NOTIFICATION, msg.length(), msg.c_str()); } #endif glClearColor(0.2, 0.2, 0.23, 1.0); auto vsId = compile_shader(GL_VERTEX_SHADER, "shaders/base.vert"); std::cout << "[GL] VS Ok." << std::endl; auto fsId = compile_shader(GL_FRAGMENT_SHADER, "shaders/base.frag"); std::cout << "[GL] FS Ok." << std::endl; program = glCreateProgram(); glAttachShader(program, vsId); glAttachShader(program, fsId); glLinkProgram(program); print_info_log(program, true); GLint link_status = 0; glGetProgramiv(program, GL_LINK_STATUS, &link_status); if (link_status != GL_TRUE) { std::cerr << "[GL] Failed to link program." << std::endl; exit(1); return; } glDeleteShader(vsId); glDeleteShader(fsId); std::cout << "[GL] Program Ok." << std::endl; glUseProgram(program); IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGui::StyleColorsDark(); ImGui_ImplSDL2_InitForOpenGL(window, context); ImGui_ImplOpenGL3_Init("#version 460"); glCreateVertexArrays(1, &vao); glBindVertexArray(vao); } char file_input[PATH_MAX]; char file_output[PATH_MAX]; int resolution[2] = { 1280, 720 }; float near = 0.001f; float far = 100.0f; float fov = 30.0f; bool update_render = false; float adjust_orientation[2] = {0, 0}; void load_data(const std::string&); void save_obj(const std::string& path); void run_ui() { ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); bool open_open = false; bool open_save = false; ImGui::Begin("Camera settings", nullptr, ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoResize); ImVec2 size = {0, 0}; ImGui::SetWindowSize("Camera settings", size, 1); if (ImGui::BeginMenuBar()) { if (ImGui::BeginMenu("File")) { if (ImGui::MenuItem("Open", "Ctrl+O")) { open_open = true; } if (ImGui::MenuItem("Save", "Ctrl+S")) { open_save = true; } ImGui::EndMenu(); } ImGui::EndMenuBar(); } if(ImGui::InputInt2("Resolution", resolution)) { update_render = true; } if(ImGui::InputFloat("Near", &near, 0, 0, "%.5f")) { update_render = true; } if(ImGui::InputFloat("Far", &far, 0, 0, "%.3f")) { update_render = true; } if (ImGui::InputFloat("FoV", &fov, 0, 0, "%.1f")) { update_render = true; } ImGui::End(); ImGui::Begin("Adjustments", nullptr, ImGuiWindowFlags_NoResize); ImGui::SetWindowSize("Adjustments", size, 1); ImGui::InputFloat2("Rotation adjust", adjust_orientation, "%.1f"); ImGui::End(); if (open_open) { ImGui::OpenPopup("file_input"); } if(ImGui::BeginPopup("file_input", ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Modal)) { ImGui::Text("Open File"); ImGui::Separator(); ImGui::InputTextWithHint("Directory Path", "Should contain a bunch of .csv", file_input, PATH_MAX, ImGuiInputTextFlags_None, nullptr, nullptr); if (ImGui::Button("Ok")) { ImGui::CloseCurrentPopup(); load_data(file_input); } ImGui::SameLine(); if (ImGui::Button("Cancel")) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } if (open_save) { ImGui::OpenPopup("file_save"); } if(ImGui::BeginPopup("file_save", ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Modal)) { ImGui::Text("Save File"); ImGui::Separator(); ImGui::InputTextWithHint("File Path", "Should end with .obj", file_output, PATH_MAX, ImGuiInputTextFlags_None, nullptr, nullptr); if (ImGui::Button("Ok")) { ImGui::CloseCurrentPopup(); std::filesystem::path target_path(file_output); if (std::filesystem::is_directory(target_path)) { target_path /= "out.obj"; } save_obj(target_path.string()); } ImGui::SameLine(); if (ImGui::Button("Cancel")) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); } struct in_vertex { float position[4]; float uv[2]; }; struct out_vertex { float position[3]; float uv[2]; }; std::vector loaded_vertices; std::vector out_vertices; GLuint data_buffer = 0; bool camera_centered = false; void load_data(const std::string& path) { std::cout << "[Main] Loading data via: " << path << std::endl; if (!std::filesystem::is_directory(path)) { std::cerr << "[Main] Could not open path." << std::endl; //TODO: Report error return; } std::vector vertices; std::filesystem::directory_iterator dir(path); while(dir != std::filesystem::directory_iterator()) { auto entry = *(dir++); if (!entry.is_regular_file() || (entry.path().filename().extension() != ".csv")) { std::cout << "[Main] Invalid file: " << entry.path() << std::endl; continue; } std::ifstream fs(entry.path()); std::stringstream line_stream; std::string def; std::getline(fs, def); line_stream.str(def); std::vector indices; while(!line_stream.eof()) { std::string idx; line_stream >> idx; if (!idx.empty()) { if (idx.ends_with(',')) { idx = idx.substr(0, idx.length() - 1); } indices.push_back(idx); } } std::string target_uvx, target_uvy; if (contains(indices, "o4.x") && contains(indices, "o4.y") && !contains(indices, "04.z")) { target_uvx = "o4.x"; target_uvy = "o4.y"; } std::string line; while(std::getline(fs, line) && !line.empty()) { size_t i = 0; std::stringstream line_stream(line); in_vertex vertex {}; bool found_x = false, found_y = false, found_z = false, found_w = false; bool found_uvx = false, found_uvy = false; while(i < indices.size()) { std::string data; line_stream >> data; if (data.empty()) { continue; } if (data.ends_with(',')) { data = data.substr(0, data.length() - 1); } auto val = std::stof(data); auto& index = indices[i]; if (index == "out_position.x") { found_x = true; vertex.position[0] = val; } if (index == "out_position.y") { found_y = true; vertex.position[1] = val; } if (index == "out_position.z") { found_z = true; vertex.position[2] = val; } if (index == "out_position.w") { found_w = true; vertex.position[3] = val; } if (index == target_uvx) { found_uvx = true; vertex.uv[0] = val; } if (index == target_uvy) { found_uvy = true; vertex.uv[1] = val; } ++i; } if (found_x && found_y && found_z && found_w && found_uvx && found_uvy) { vertices.push_back(vertex); } } } loaded_vertices = std::move(vertices); std::cout << "[Main] Loaded " << loaded_vertices.size() << " vertoces." << std::endl; update_render = true; camera_centered = false; } float mouse_sensitivity = 0.005; float camera_yaw = 0, camera_pitch = 0; glm::vec3 camera_position {}; glm::quat camera_orientation {}; glm::mat4 display_proj = glm::identity(); glm::mat4 display_view = glm::identity(); glm::mat4 display_model = glm::identity(); glm::vec3 center = {}; float size = 0.f; void save_obj(const std::string& path) { std::ofstream out(path); if (out_vertices.size() % 3 != 0) { std::cerr << "Critical error" << std::endl; exit(1); return; } for(auto&[position, uv] : out_vertices) { glm::vec4 original_pos = glm::vec4(position[0], position[1], position[2], 1.0); glm::vec3 target_pos = glm::xyz(display_model * original_pos); out << "v " << target_pos.x << " " << target_pos.y << " " << target_pos.z << "\n"; out << "vt " << uv[0] << " " << (1.0f - uv[1]) << "\n"; } for(size_t i = 1; i <= out_vertices.size(); i += 3) { out << "f " << i + 2 << "/" << i + 2 << " " << i + 1 << "/" << i + 1 << " " << i << "/" << i << "\n"; } } void update_camera() { glm::mat4 m = glm::translate(glm::identity(), -camera_position); camera_orientation = glm::identity(); camera_orientation = glm::rotate(camera_orientation, camera_pitch, {1, 0, 0}); camera_orientation = glm::rotate(camera_orientation, camera_yaw, {0, 1, 0}); display_view = glm::mat4(camera_orientation) * m; } void update_adjustments() { display_model = glm::rotate(glm::identity(), glm::radians(adjust_orientation[0]), {1, 0, 0}); display_model = glm::rotate(display_model, glm::radians(adjust_orientation[1]), {0, 1, 0}); } bool validate_params() { return fov > 0 && resolution[0] > 0 && resolution[1] > 0 && near > 0 && far > 0; } void run_gl(SDL_Window* window) { glUseProgram(program); if (update_render && !loaded_vertices.empty() && validate_params()) { std::cout << "[Main] Params: " << fov << " " << resolution[0] << "x" << resolution[1] << " " << near << " " << far << std::endl; glm::mat4 proj = glm::perspectiveFov(glm::radians(fov), resolution[0], resolution[1], near, far); glm::mat4 inv = glm::inverse(proj); std::cout << "[Main] Generating data..." << std::endl; out_vertices.clear(); glm::vec3 max(-100000, -10000, -10000); glm::vec3 min(10000, 10000, 10000); for(auto& in : loaded_vertices) { glm::vec4 pos = { in.position[0], in.position[1], in.position[2], in.position[3] }; glm::vec4 reverse = inv * pos; reverse /= reverse.w; min = glm::min(glm::xyz(reverse), min); max = glm::max(glm::xyz(reverse), max); out_vertices.push_back({{reverse.x, reverse.y, reverse.z}, {in.uv[0], in.uv[1]}}); } center = glm::mix(min, max, 0.5f); auto dist = glm::length(max - min) / 2; size = dist; if (!camera_centered) { camera_yaw = 0; camera_pitch = 0; camera_centered = true; } for(auto& v : out_vertices) { v.position[0] -= center.x; v.position[1] -= center.y; v.position[2] -= center.z; } if (data_buffer != 0) { glDeleteBuffers(1, &data_buffer); } glGenBuffers(1, &data_buffer); glBindBuffer(GL_ARRAY_BUFFER, data_buffer); glBufferData(GL_ARRAY_BUFFER, sizeof(out_vertex) * out_vertices.size(), out_vertices.data(), GL_STATIC_DRAW); update_render = false; } if (data_buffer != 0) { update_camera(); update_adjustments(); int size_x = 0, size_y = 0; SDL_GetWindowSize(window, &size_x, &size_y); display_proj = glm::perspectiveFov(glm::radians(90.0f), size_x, size_y, 0.01f, 1000.0f); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(out_vertex), nullptr); glUniformMatrix4fv(0, 1, false, glm::value_ptr(display_model)); glUniformMatrix4fv(1, 1, false, glm::value_ptr(display_view)); glUniformMatrix4fv(2, 1, false, glm::value_ptr(display_proj)); glUniform4f(3, 0.9f, 0.9f, 0.9f, 1.0f); glDrawArrays(GL_TRIANGLES, 0, out_vertices.size()); } run_ui(); } glm::vec3 camera_movement { 0, 0, 0}; bool rotating = false; int main(int argc, char** argv) { setenv("SDL_VIDEODRIVER", "wayland", 1); SDL_Init(SDL_INIT_VIDEO); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); #ifdef __DEBUG SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); #endif auto* win = SDL_CreateWindow("ModelExtractor", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1290, 720, SDL_WINDOW_OPENGL); auto* ctx = SDL_GL_CreateContext(win); SDL_ShowWindow(win); SDL_GL_MakeCurrent(win, ctx); glewInit(); init_gl(win, ctx); SDL_Event ev; bool alive = true; while(alive) { camera_movement = {0, 0, 0}; while(SDL_PollEvent(&ev)) { ImGui_ImplSDL2_ProcessEvent(&ev); switch(ev.type) { case SDL_EventType::SDL_QUIT: alive = false; break; case SDL_EventType::SDL_MOUSEMOTION: if (rotating) { float rot_y = mouse_sensitivity * ev.motion.xrel; float rot_x = mouse_sensitivity * ev.motion.yrel; camera_yaw += rot_y; camera_pitch += rot_x; } break; case SDL_EventType::SDL_MOUSEBUTTONDOWN: if (!ImGui::GetIO().WantCaptureMouse) { rotating = true; SDL_CaptureMouse(SDL_TRUE); SDL_SetWindowMouseGrab(win, SDL_TRUE); } break; case SDL_EventType::SDL_KEYDOWN: if (!ImGui::GetIO().WantCaptureKeyboard) { switch(ev.key.keysym.sym) { case SDL_KeyCode::SDLK_w: camera_movement.z -= 1.0f; break; case SDL_KeyCode::SDLK_s: camera_movement.z += 1.0f; break; case SDL_KeyCode::SDLK_a: camera_movement.x -= 1.0f; break; case SDL_KeyCode::SDLK_d: camera_movement.x += 1.0f; break; case SDL_KeyCode::SDLK_ESCAPE: rotating = false; SDL_CaptureMouse(SDL_FALSE); SDL_SetWindowMouseGrab(win, SDL_FALSE); break; } } break; } } if (!ImGui::IsAnyItemFocused()) { camera_position += glm::xyz(glm::vec4((camera_movement * 0.05f * size), 0.0) * glm::mat4(camera_orientation)); } glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); run_gl(win); SDL_GL_SwapWindow(win); } if (data_buffer != 0) { glDeleteBuffers(1, &data_buffer); } glDeleteVertexArrays(1, &vao); ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplSDL2_Shutdown(); ImGui::DestroyContext(); SDL_GL_MakeCurrent(win, nullptr); SDL_GL_DeleteContext(ctx); SDL_DestroyWindow(win); SDL_Quit(); return 0; }