/* * MIT License * * Copyright (c) 2026 Mark Allyn * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ // Author: Mark Allyn // // dot_animation/main.cpp // // Position and color are computed entirely inside the shaders from a single // u_anim_time uniform. The CPU only measures wall-clock time and forwards it // each frame — no per-frame state variables on the CPU side. #include #include #include #include // --------------------------------------------------------------------------- // Vertex shader // // Derives x and y from u_anim_time: // 60 steps per second; x and y each advance 0.01 per step. // Both reset to 0 when x reaches 0.9 (every 90 steps). // u_anim_time is negative during the initial 1-second pause — dot sits at center. // --------------------------------------------------------------------------- static const char* vertex_shader_src = R"glsl( #version 330 core uniform float u_anim_time; void main() { float x = 0.0; float y = 0.0; if (u_anim_time >= 0.0) { float step_f = floor(u_anim_time * 60.0); float x_step = mod(step_f, 90.0); x = x_step * 0.01; y = x_step * 0.01; } gl_Position = vec4(x, y, 0.0, 1.0); gl_PointSize = 4.0; } )glsl"; // --------------------------------------------------------------------------- // Fragment shader // // Derives color from u_anim_time using total (never-reset) step count: // Steps 0-254: red fades 1.0 -> 0.0 (1/255 per step), green=1, blue=1 // Step 255+: red rises +0.01/step, green falls -0.01/step, blue=1 // --------------------------------------------------------------------------- static const char* fragment_shader_src = R"glsl( #version 330 core uniform float u_anim_time; out vec4 frag_color; void main() { float red = 1.0; float green = 1.0; if (u_anim_time >= 0.0) { float total_step = floor(u_anim_time * 60.0); if (total_step < 255.0) { red = 1.0 - total_step / 255.0; } else { float steps2 = total_step - 255.0; red = clamp(steps2 * 0.01, 0.0, 1.0); green = clamp(1.0 - steps2 * 0.01, 0.0, 1.0); } } frag_color = vec4(red, green, 1.0, 1.0); } )glsl"; // --------------------------------------------------------------------------- // compile_shader // --------------------------------------------------------------------------- static GLuint compile_shader(GLenum type, const char* src) { GLuint shader = glCreateShader(type); glShaderSource(shader, 1, &src, nullptr); glCompileShader(shader); GLint ok = 0; glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); if (!ok) { char log[512]; glGetShaderInfoLog(shader, sizeof(log), nullptr, log); std::fprintf(stderr, "Shader compile error:\n%s\n", log); } return shader; } // --------------------------------------------------------------------------- // build_program // --------------------------------------------------------------------------- static GLuint build_program(const char* vert_src, const char* frag_src) { GLuint vert = compile_shader(GL_VERTEX_SHADER, vert_src); GLuint frag = compile_shader(GL_FRAGMENT_SHADER, frag_src); GLuint program = glCreateProgram(); glAttachShader(program, vert); glAttachShader(program, frag); glLinkProgram(program); GLint ok = 0; glGetProgramiv(program, GL_LINK_STATUS, &ok); if (!ok) { char log[512]; glGetProgramInfoLog(program, sizeof(log), nullptr, log); std::fprintf(stderr, "Program link error:\n%s\n", log); } glDeleteShader(vert); glDeleteShader(frag); return program; } // --------------------------------------------------------------------------- // main // --------------------------------------------------------------------------- int main() { if (!glfwInit()) { std::fprintf(stderr, "glfwInit failed\n"); return EXIT_FAILURE; } glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); GLFWwindow* window = glfwCreateWindow(800, 600, "Dot Animation", nullptr, nullptr); if (!window) { std::fprintf(stderr, "glfwCreateWindow failed\n"); glfwTerminate(); return EXIT_FAILURE; } glfwMakeContextCurrent(window); if (!gladLoadGLLoader(reinterpret_cast(glfwGetProcAddress))) { std::fprintf(stderr, "GLAD init failed\n"); return EXIT_FAILURE; } glEnable(GL_PROGRAM_POINT_SIZE); GLuint shader_program = build_program(vertex_shader_src, fragment_shader_src); GLint loc_anim_time = glGetUniformLocation(shader_program, "u_anim_time"); /* Empty VAO — vertex shader derives position from the uniform, no vertex attributes are needed. */ GLuint vao = 0; glGenVertexArrays(1, &vao); /* anim_start is set 1 second into the future so that u_anim_time is negative for the first second (dot held at center, white). */ double anim_start = glfwGetTime() + 1.0; while (!glfwWindowShouldClose(window)) { if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, GLFW_TRUE); float anim_time = static_cast(glfwGetTime() - anim_start); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(shader_program); glUniform1f(loc_anim_time, anim_time); glBindVertexArray(vao); glDrawArrays(GL_POINTS, 0, 1); glBindVertexArray(0); glfwSwapBuffers(window); glfwPollEvents(); } glDeleteVertexArrays(1, &vao); glDeleteProgram(shader_program); glfwDestroyWindow(window); glfwTerminate(); return EXIT_SUCCESS; }