diff --git a/CMakeLists.txt b/CMakeLists.txt index 1aa3865..1227c23 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,10 +94,13 @@ endif() # Link with OpenGL + dependencies if (APPLE) - target_link_libraries(${EXECUTABLE_NAME} PRIVATE glfw glad glm::glm "-framework OpenGL") + find_package(OpenGL REQUIRED) + find_package(CURL REQUIRED) + target_link_libraries(${EXECUTABLE_NAME} PRIVATE glfw glad glm::glm "-framework OpenGL -lcurl") elseif (UNIX) find_package(OpenGL REQUIRED) - target_link_libraries(${EXECUTABLE_NAME} PRIVATE glfw glad glm::glm OpenGL::GL) + find_package(CURL REQUIRED) + target_link_libraries(${EXECUTABLE_NAME} PRIVATE glfw glad glm::glm OpenGL::GL "-lpthread -ldl -lcurl") elseif (WIN32) target_link_libraries(${EXECUTABLE_NAME} PRIVATE glfw glad glm::glm opengl32) endif() diff --git a/RASTER b/RASTER index ba70312..35bf84e 100755 Binary files a/RASTER and b/RASTER differ diff --git a/shaders/fragment.glsl b/shaders/fragment.glsl index c6ccdf8..656c13c 100644 --- a/shaders/fragment.glsl +++ b/shaders/fragment.glsl @@ -4,143 +4,12 @@ precision mediump float; #endif +in vec2 vUV; out vec4 FragColor; // Output to screen -uniform vec2 u_resolution; // <-- screen resolution from CPU -uniform float u_time; // <-- time from CPU -uniform float u_scale; // <-- scale from CPU -uniform vec2 u_mouse; // <-- mouse from CPU - -vec3 hsv2rgb(vec3 c) { - vec3 rgb = clamp( - abs(mod(c.x*6.0 + vec3(0.0,4.0,2.0), - 6.0) - 3.0) - 1.0, - 0.0, - 1.0 - ); - return c.z * mix(vec3(1.0), rgb, c.y); -} - -vec3 getPaletteColor(float idx, float seed) { - float baseHue = fract(seed); // 0..1 base hue - float sat = 0.9; - float val = 0.9; - float hue = fract(baseHue + (idx * 0.33)); - return hsv2rgb(vec3(hue, sat, val)); -} - -vec2 random(vec2 p) { - return normalize(vec2( - fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453), - fract(sin(dot(p, vec2(269.5, 183.3))) * 43758.5453) - ) * 2.0 - 1.0); -} - -float perlinNoise(vec2 st) { - vec2 i = floor(st); - vec2 f = fract(st); - - vec2 g00 = random(i + vec2(0.0,0.0)); - vec2 g10 = random(i + vec2(1.0,0.0)); - vec2 g01 = random(i + vec2(0.0,1.0)); - vec2 g11 = random(i + vec2(1.0,1.0)); - - float n00 = dot(g00, f - vec2(0.0,0.0)); - float n10 = dot(g10, f - vec2(1.0,0.0)); - float n01 = dot(g01, f - vec2(0.0,1.0)); - float n11 = dot(g11, f - vec2(1.0,1.0)); - - vec2 u = f*f*(3.0-2.0*f); - return mix(mix(n00, n10, u.x), mix(n01, n11, u.x), u.y); -} - -mat2 rotate2d(float angle){ - return mat2(cos(angle),-sin(angle), - sin(angle),cos(angle)); -} - -vec2 morphingSeed(float t, float interval) { - float phase = floor(t / interval); - float blend = fract(t / interval); - - vec2 seedA = vec2( - sin(phase * 12.9898), - cos(phase * 78.233) - ); - vec2 seedB = vec2( - sin((phase+1.0) * 12.9898), - cos((phase+1.0) * 78.233) - ); - - blend = smoothstep(0.0, 1.0, blend); - return mix(seedA, seedB, blend); -} +uniform float u_time; // Time in seconds void main() { - vec2 st = gl_FragCoord.xy / u_resolution.xy; // Normalize coordinates - st.x *= u_resolution.x / u_resolution.y; // Correct aspect ratio - st -= 0.5; // Center coordinates - st += vec2(100.0,40.0); - - st *= 2.05 * u_scale; // Zoom out - vec2 seed = morphingSeed(u_time, 10.0); // 10 second interval - - vec2 pos = st; // 15 second interval - pos += vec2(0.3, 0.1) * u_time * 0.1; - - float pre_noise = perlinNoise((pos) * 1.5); // N = [0..1] - // noise += 0.6 * perlinNoise(pos * 4.0); - - pos += rotate2d(pre_noise) * pos * 0.2; - pos += u_time * 0.5; - - float noise = perlinNoise(pos + seed * 2.0); - // noise = smoothstep(0.0, 1.0, noise); // Smooth the noise - - // Normalize noise to [0..1] - noise = noise + 0.5; - - vec3 color; - - if (noise >= 0.85) { - color = getPaletteColor(u_time * 0.1, noise); - } else if (noise >= 0.75 ) { - color = getPaletteColor((u_time * 0.1) + 1.0, noise); - - // Estimate gradient (2D normal) - vec2 eps = vec2(0.002, 0.0); - float n0 = perlinNoise(pos + seed * 2.0); - float nx = perlinNoise(pos + seed * 2.0 + eps.xy) - n0; - float ny = perlinNoise(pos + seed * 2.0 + eps.yx) - n0; - vec2 normal = normalize(vec2(nx, ny)); - - // View direction (from pixel to "camera") - vec2 viewDir = normalize(-st); - - // Light direction (moves over time) - vec2 lightDir = normalize(vec2(cos(u_time * 0.2), sin(u_time * 0.3))); - - // Fresnel term (stronger highlight at grazing angles) - float fresnel = pow(1.0 - max(dot(viewDir, normal), 0.0), 3.0); - - // Specular component - float spec = pow(max(dot(normal, lightDir), 0.0), 32.0); - - // Combine Fresnel + spec - float gloss = fresnel + 0.6 * spec; - - // Stronger at edges (transition zone of blob) - float edge = smoothstep(0.8, 0.9, noise) * (1.0 - smoothstep(0.9, 1.0, noise)); - gloss *= edge * 2.0; - - // Add colored glossy highlight (slightly bluish tint) - color += gloss * vec3(0.8, 0.9, 1.0); - } else { - color = vec3(0.0); - } - - // Denoise edges -> "Insane look-alike effect" - // color = mix(color, vec3(0.0), smoothstep(0.0, 0.02, abs(noise - 0.9))); - + vec3 color = vec3(vUV, 0.0); FragColor = vec4(color, 1.0); } diff --git a/shaders/vertex.glsl b/shaders/vertex.glsl index 2dbd36d..51ccf58 100644 --- a/shaders/vertex.glsl +++ b/shaders/vertex.glsl @@ -1,13 +1,10 @@ #version 410 core -layout(location = 0) in vec3 aPos; // Vertex position - -uniform mat4 model; -uniform mat4 view; -uniform mat4 projection; - +layout(location = 0) in vec2 aPos; // Vertex position +out vec2 vUV; void main() { - gl_Position = projection * view * model * vec4(aPos, 1.0); + vUV = (aPos + 1.0) / 2.0; // NDC [-1,1] -> UV [0,1] + gl_Position = vec4(aPos, 0.0, 1.0); } diff --git a/source/main.cpp b/source/main.cpp index cbdd8f8..ff2a742 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,14 +1,12 @@ -#include #include +#include #include #include #include -#define GL_SILENCE_DEPRECATION - // glad and GLFW -#include #include +#include // glm math #include @@ -18,11 +16,15 @@ // HPP files #include "shader.h" -//imGui +// imGui #include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" +// CURL +#include "http_get.h" +#include + #define VERTEX_FILE "./shaders/vertex.glsl" #define FRAGMENT_FILE "./shaders/fragment.glsl" #define COMPUTE_FILE "./shaders/compute.glsl" @@ -31,134 +33,261 @@ const float SCR_WIDTH = 1200.0f; const float SCR_HEIGHT = 1200.0f; +unsigned char apiExist = 0; +unsigned char apiIsValid = 0; +unsigned char isVsync = 1; +char savedLocation[128] = "Lisbon"; +float location_latitude = -1; // default invalid location_latitude +float location_longitude = -1; // default invalid location_longitude + +int currentAA = 4; // 0=off, 2=2x, 4=4x, 8=8x + int main(void) { - // Initialize GLFW - if (!glfwInit()) return -1; + // Initialize GLFW + if (!glfwInit()) + return -1; - // Set OpenGL version (4.1 Core) and other window hints - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); - glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // REQUIRED on macOS - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - glfwWindowHint(GLFW_SAMPLES, 8); // 8x MSAA + // Set OpenGL version (4.1 Core) and other window hints + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // REQUIRED on macOS + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_SAMPLES, currentAA); - GLFWwindow* window = glfwCreateWindow((int)SCR_WIDTH, (int)SCR_HEIGHT, "RASTER", nullptr, nullptr); - if (!window) { - glfwTerminate(); - return -1; - } - glfwMakeContextCurrent(window); + GLFWwindow *window = glfwCreateWindow((int)SCR_WIDTH, (int)SCR_HEIGHT, "RASTER", nullptr, nullptr); + if (!window) { + glfwTerminate(); + return -1; + } + glfwMakeContextCurrent(window); - if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { - std::cerr << "Failed to initialize GLAD\n"; - return -1; - } + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { + std::cerr << "Failed to initialize GLAD\n"; + return -1; + } - glViewport(0, 0, (int)SCR_WIDTH, (int)SCR_HEIGHT); + glViewport(0, 0, (int)SCR_WIDTH, (int)SCR_HEIGHT); - //DEBUG - std::cout << "OpenGL Version: " << glGetString(GL_VERSION) << std::endl; + // DEBUG + std::cout << "OpenGL Version: " << glGetString(GL_VERSION) << std::endl; - // Setup Dear ImGui context - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls - io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO &io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls - // Setup Platform/Renderer backends - ImGui_ImplGlfw_InitForOpenGL(window, true); // Second param install_callback=true will install GLFW callbacks and chain to existing ones. - ImGui_ImplOpenGL3_Init(); + // Setup Platform/Renderer backends + ImGui_ImplGlfw_InitForOpenGL(window, true); // Second param install_callback=true will install GLFW + // callbacks and chain to existing ones. + ImGui_ImplOpenGL3_Init(); - //DEBUG - std::cout << "ImGui Version: " << IMGUI_VERSION << std::endl; - std::string api_key = ""; + // DEBUG + std::cout << "ImGui Version: " << IMGUI_VERSION << std::endl; + std::string api_key = ""; - // Get API keys from .env file - std::ifstream envFile(ENV_FILE); - if (envFile.is_open()) { - std::string line; - while (std::getline(envFile, line)) { - // std::cout << line << std::endl; // Print each line (for debugging) - // Here you can parse the line to extract key-value pairs if needed + // Get API keys from .env file + std::ifstream envFile(ENV_FILE); + if (envFile.is_open()) { + std::string line; - if (line.find("API_KEY=") == 0) { - api_key = line.substr(8); // Extract value after "API_KEY=" - } - } - envFile.close(); - } else { - std::cerr << "Could not open the .env file." << std::endl; - } + while (std::getline(envFile, line)) { + // std::cout << line << std::endl; // Print each line (for + // debugging) Here you can parse the line to extract key-value pairs + // if needed - // ERROR: macOS does not support compute shaders in OpenGL < 4.3. - // GLuint computeProgram = createComputeProgram(COMPUTE_FILE); + if (line.find("WEATHER_API_KEY=") == 0) { + api_key = line.substr(16); // Extract value after "WEATHER_API_KEY=" + apiExist = 1; + } + } + envFile.close(); + } else { + std::cerr << "Could not open the .env file." << std::endl; + } - // Load shaders - GLuint shaderProgram = createShaderProgram(VERTEX_FILE, FRAGMENT_FILE); + // ERROR: macOS does not support compute shaders in OpenGL < 4.3. + // GLuint computeProgram = createComputeProgram(COMPUTE_FILE); - // Check if shader program was created successfully - if (shaderProgram == 0) { - std::cerr << "Failed to create shader programs.\n"; - return -1; - } + // Load shaders + GLuint shaderProgram = createShaderProgram(VERTEX_FILE, FRAGMENT_FILE); - // FPS tracking - double lastTime = glfwGetTime(); // when we last printed FPS - double fps = 0.0; - int frameCount = 0; // frames since last FPS print - float mousePos[2] = {0.0f, 0.0f}; + // Quad to render vertex too + float vertices[] = {// positions + -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f}; - // Render loop - while (!glfwWindowShouldClose(window)) { - // Time - float timeValue = (float)glfwGetTime(); - mousePos[0] = (float)io.MousePos.x; - mousePos[1] = (float)io.MousePos.y; + unsigned int indices[] = {0, 1, 2, 2, 1, 3}; - // Input - glfwPollEvents(); + GLuint VAO, VBO, EBO; + glGenVertexArrays(1, &VAO); + glGenBuffers(1, &VBO); + glGenBuffers(1, &EBO); - // Start the Dear ImGui frame - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); + glBindVertexArray(VAO); + glBindBuffer(GL_ARRAY_BUFFER, VBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); - // --- FPS Counter --- - double currentTime = glfwGetTime(); - double delta = currentTime - lastTime; + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); - if (delta >= 1.0) { // print every ~1 second - fps = double(frameCount) / delta; - std::cout << "FPS: " << fps - << " Time: " << timeValue - << " Mouse: (" << mousePos[0] << ", " << mousePos[1] << ")" - << std::endl; + // positions + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0); + glEnableVertexAttribArray(0); - frameCount = 0; - lastTime = currentTime; - } + glBindVertexArray(0); - frameCount++; + glEnable(GL_MULTISAMPLE); - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - glUseProgram(shaderProgram); + // FPS tracking + double lastTime = glfwGetTime(); // when we last printed FPS + double fps = 0.0; + int frameCount = 0; // frames since last FPS print + float mousePos[2] = {0.0f, 0.0f}; - ImGui::Render(); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + // Check if API key is valid (dummy check here, implement actual validation + // as needed) + char *url = (char *)malloc(1024); + if (apiExist) { + snprintf(url, 1024, "http://api.openweathermap.org/geo/1.0/direct?q=%s&appid=%s", savedLocation, api_key.c_str()); + char *response = http_get(url); + if (response) { + apiIsValid = 1; + // Extract latitude and longitude from response (simple parsing, + // improve as needed) + std::string respStr(response); + size_t latPos = respStr.find("\"lat\":"); + size_t lonPos = respStr.find("\"lon\":"); - glfwSwapBuffers(window); - } + if (latPos != std::string::npos && lonPos != std::string::npos) { + sscanf(respStr.c_str() + latPos, "\"lat\":%f", &location_latitude); + sscanf(respStr.c_str() + lonPos, "\"lon\":%f", &location_longitude); + std::cout << "Location: " << savedLocation << " Latitude: " << location_latitude << " Longitude: " << location_longitude << std::endl; + } else { + std::cerr << "Could not parse latitude and longitude from API " + "response." + << std::endl; + apiIsValid = 0; + } - glDeleteProgram(shaderProgram); + free(response); + } else { + apiIsValid = 0; + } + } - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplGlfw_Shutdown(); - ImGui::DestroyContext(); + char *currentWeather = (char *)malloc(1024); // buffer for current weather data + snprintf(currentWeather, 1024, "No data"); - glfwDestroyWindow(window); - glfwTerminate(); - return 0; + // Render loop + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + + // Time + float timeValue = (float)glfwGetTime(); + mousePos[0] = (float)io.MousePos.x; + mousePos[1] = (float)io.MousePos.y; + + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + glUseProgram(shaderProgram); + GLint resLoc = glGetUniformLocation(shaderProgram, "u_resolution"); + glUniform2f(resLoc, (float)SCR_WIDTH, (float)SCR_HEIGHT); + + GLint timeLoc = glGetUniformLocation(shaderProgram, "u_time"); + glUniform1f(timeLoc, timeValue); + + // draw quad + glBindVertexArray(VAO); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + // API request to get weather data + if (((int)(timeValue * 100)) % 1000 == 1 && apiExist && apiIsValid) { + snprintf(url, 1024, "http://api.openweathermap.org/data/2.5/direct?q=%s&appid=%s", savedLocation, api_key.c_str()); + char *response = http_get(url); + if (response) { + snprintf(currentWeather, 1024, "%s", response); + free(response); + } + } + // Start the Dear ImGui frame + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + ImGui::Begin("Debug Window"); + ImGui::Text("FPS: %.1f", fps); + ImGui::Text("Time: %.2f seconds", timeValue); + ImGui::Separator(); + ImGui::Text("Options:"); + // V-Sync options + if (ImGui::Checkbox("V-Sync", (bool *)&isVsync)) { + glfwSwapInterval(isVsync ? 1 : 0); + } + ImGui::Separator(); + ImGui::Text("API Key Status:"); + if (apiExist) { + ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "\t\tAPI Key Loaded OK"); + } else { + ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "\t\tAPI Key Missing or Invalid!"); + } + if (apiIsValid) { + ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "\t\tAPI Key Validated OK"); + } else { + ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "\t\tAPI Key Not Validated!"); + } + ImGui::Separator(); + ImGui::Text("Program Info:"); + ImGui::Text("\t\tOpenGL Version: %s", glGetString(GL_VERSION)); + ImGui::Text("\t\tShader Program: %s", shaderProgram ? "Loaded" : "Error"); + + ImGui::End(); + + ImGui::Begin("Weather Data"); + if (apiExist && apiIsValid) { + ImGui::TextWrapped("%s", currentWeather); + } else if (!apiExist) { + ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "No API Key found in .env file!"); + } else if (!apiIsValid) { + ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "API Key is not valid!"); + } + ImGui::Separator(); + ImGui::Text("API URL: %s", url); + ImGui::End(); + + // --- FPS Counter --- + double currentTime = glfwGetTime(); + double delta = currentTime - lastTime; + + if (delta >= 1.0) { // print every ~1 second + fps = double(frameCount) / delta; + /* std::cout << "FPS: " << fps + << " Time: " << timeValue + << " Mouse: (" << mousePos[0] << ", " << mousePos[1] << ")" + << std::endl; + */ + frameCount = 0; + lastTime = currentTime; + } + + frameCount++; + + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + glfwSwapBuffers(window); + } + + glDeleteProgram(shaderProgram); + + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + + glfwDestroyWindow(window); + glfwTerminate(); + free(url); + free(currentWeather); + return 0; } - diff --git a/source/shader.cpp b/source/shader.cpp index 8600409..97cf296 100644 --- a/source/shader.cpp +++ b/source/shader.cpp @@ -1,5 +1,8 @@ #include "shader.h" +#define GL_SILENCE_DEPRECATION + + // Shader loader helper std::string loadShaderSource(const std::string& filepath) { std::ifstream file(filepath); @@ -9,7 +12,7 @@ std::string loadShaderSource(const std::string& filepath) { } std::stringstream buffer; - std::cout << "Loaded " << filepath << " successfully!\n"; + // std::cout << "Loaded " << filepath << " successfully!\n"; buffer << file.rdbuf(); return buffer.str();