228 lines
6.8 KiB
C++
228 lines
6.8 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
|
|
//
|
|
// 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 <glad/glad.h>
|
|
#include <GLFW/glfw3.h>
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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<GLADloadproc>(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<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_POINTS, 0, 1);
|
|
glBindVertexArray(0);
|
|
|
|
glfwSwapBuffers(window);
|
|
glfwPollEvents();
|
|
}
|
|
|
|
glDeleteVertexArrays(1, &vao);
|
|
glDeleteProgram(shader_program);
|
|
|
|
glfwDestroyWindow(window);
|
|
glfwTerminate();
|
|
return EXIT_SUCCESS;
|
|
}
|