/* * 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 // // sweep_line/main.cpp // // A green vertical line sweeps left to right across a black screen at // 0.001 clip-space units per step (60 steps/sec). Behind the line, a // phosphor-style trail fades from red (right behind the line) to black // (at the left edge when the line reaches the right edge). Then repeats. // // A fullscreen quad is drawn every frame; the fragment shader computes // line position, trail color, and fade entirely from u_anim_time. #include #include #include #include // --------------------------------------------------------------------------- // Vertex shader // // Covers the screen with a fullscreen quad. v_pos carries the clip-space // x,y of each fragment to the fragment shader. // --------------------------------------------------------------------------- static const char* vertex_shader_src = R"glsl( #version 330 core layout(location = 0) in vec2 a_pos; out vec2 v_pos; void main() { v_pos = a_pos; gl_Position = vec4(a_pos, 0.0, 1.0); } )glsl"; // --------------------------------------------------------------------------- // Fragment shader // // sweep_x travels from -1.0 to +1.0 (2.0 clip-space units) at // 0.001 units/step × 60 steps/sec = 0.06 units/sec → one full sweep in ~33 s. // // For each fragment at clip-space x = px: // px ≈ sweep_x → green (current line) // px < sweep_x → red trail: intensity = 1 - dist/2 (full red at // line, black when dist == 2.0, i.e. when sweep_x // is at +1.0 and px is at -1.0) // px > sweep_x → black // --------------------------------------------------------------------------- static const char* fragment_shader_src = R"glsl( #version 330 core uniform float u_anim_time; in vec2 v_pos; out vec4 frag_color; const float STEPS_PER_SEC = 60.0; const float STEP_SIZE = 0.001; const float SWEEP_RANGE = 2.0; // clip space: -1.0 to +1.0 const float LINE_HALF_W = 0.004; // half-width of green line in clip space void main() { float px = v_pos.x; float step_f = floor(u_anim_time * STEPS_PER_SEC); float travel = mod(step_f * STEP_SIZE, SWEEP_RANGE); float sweep_x = -1.0 + travel; // Current sweep line: green if (abs(px - sweep_x) <= LINE_HALF_W) { frag_color = vec4(0.0, 1.0, 0.0, 1.0); return; } // Phosphor trail behind the line: red fading to black if (px < sweep_x) { float dist = sweep_x - px; // 0 at line edge, up to 2.0 float intensity = clamp(1.0 - dist / SWEEP_RANGE, 0.0, 1.0); frag_color = vec4(intensity, 0.0, 0.0, 1.0); return; } // Ahead of the sweep line: black frag_color = vec4(0.0, 0.0, 0.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, "Sweep Line", 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; } GLuint shader_program = build_program(vertex_shader_src, fragment_shader_src); GLint loc_anim_time = glGetUniformLocation(shader_program, "u_anim_time"); /* Fullscreen quad as a triangle strip: (-1,-1) → (1,-1) → (-1,1) → (1,1) */ float quad_verts[] = { -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, }; GLuint vao = 0; GLuint vbo = 0; glGenVertexArrays(1, &vao); glGenBuffers(1, &vbo); glBindVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(quad_verts), quad_verts, GL_STATIC_DRAW); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * static_cast(sizeof(float)), reinterpret_cast(0)); glEnableVertexAttribArray(0); glBindVertexArray(0); double anim_start = glfwGetTime(); 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_TRIANGLE_STRIP, 0, 4); glBindVertexArray(0); glfwSwapBuffers(window); glfwPollEvents(); } glDeleteVertexArrays(1, &vao); glDeleteBuffers(1, &vbo); glDeleteProgram(shader_program); glfwDestroyWindow(window); glfwTerminate(); return EXIT_SUCCESS; }