253 lines
7.9 KiB
C++
253 lines
7.9 KiB
C++
/*
|
||
* 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 <glad/glad.h>
|
||
#include <GLFW/glfw3.h>
|
||
|
||
#include <cstdio>
|
||
#include <cstdlib>
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 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<GLADloadproc>(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<int>(sizeof(float)),
|
||
reinterpret_cast<void*>(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<float>(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;
|
||
}
|