/// Includes
#include <ctime>
#include <sstream>
#include <fstream>
#include <iostream>
#include <iomanip>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
/// Constants
constexpr auto width = 419;
constexpr auto height = 293;
constexpr auto title = "sdf-winding";
/// Helpers
//// Debug
void GLAPIENTRY debug_message_callback(
GLenum /* source */,
GLenum /* type */,
GLuint /* id */,
GLenum /* severity */,
GLsizei /* length */,
GLchar const * message,
void const * /* param */
)
{
std::cerr << message << std::endl;
};
//// Uniform
void uniform(GLint location, int value ) { glUniform1i (location, value); }
void uniform(GLint location, bool value ) { glUniform1i (location, value); }
void uniform(GLint location, float value ) { glUniform1f (location, value); }
void uniform(GLint location, float const (& value)[1]) { glUniform1fv(location, 1, value); }
void uniform(GLint location, float const (& value)[2]) { glUniform2fv(location, 1, value); }
void uniform(GLint location, float const (& value)[3]) { glUniform3fv(location, 1, value); }
void uniform(GLint location, float const (& value)[4]) { glUniform4fv(location, 1, value); }
template<typename T>
void uniform(char const * name, T const & value)
{
GLuint program; glGetIntegerv(GL_CURRENT_PROGRAM, (GLint *)&program);
GLint location = glGetUniformLocation(program, name);
uniform(location, value);
}
//// Write
void write_tga(char const * base)
{
char header[] = {
0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0,
(char)(width >> 0U),
(char)(width >> 8U),
(char)(height >> 0U),
(char)(height >> 8U),
4*8, 0,
};
char data[4*width*height];
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, data);
auto t = std::time(nullptr);
auto pt = std::put_time(std::localtime(&t), "%Y-%m-%d_%H-%M-%S");
std::ofstream((std::ostringstream{} << base << "_" << pt << ".tga").str())
.write(header, sizeof(header))
.write(data, sizeof(data));
}
/// Main
int main(int /*argc*/, char * argv[])
{
//// Window/context
glfwInit();
auto window = glfwCreateWindow(width, height, title, nullptr, nullptr);
glfwMakeContextCurrent(window);
glewInit();
//// Callbacks
glfwSetWindowUserPointer(window, argv[0]);
glfwSetKeyCallback(window,
[](GLFWwindow* w, int key, int /*scancode*/, int action, int /*mods*/)
{
auto ptr = glfwGetWindowUserPointer(w);
if (action != GLFW_RELEASE) return;
if (key == GLFW_KEY_Q) glfwSetWindowShouldClose(w, GLFW_TRUE);
if (key == GLFW_KEY_W) write_tga((char *)ptr);
}
);
//// Debug
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
glDebugMessageCallback(debug_message_callback, nullptr);
//// Shader
auto make_program = [](char const * vert_source, char const * frag_source)
{
auto program = glCreateProgram();
auto vert = glCreateShader(GL_VERTEX_SHADER);
auto frag = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(vert, 1, &vert_source, nullptr);
glShaderSource(frag, 1, &frag_source, nullptr);
glCompileShader(vert);
glCompileShader(frag);
glAttachShader(program, vert);
glAttachShader(program, frag);
glLinkProgram(program);
return program;
};
auto vert_source = R"(
#version 140
out vec2 tex_coord;
void main()
{
tex_coord = vec2[](
vec2(0, 0),
vec2(2, 0),
vec2(0, 2)
)[gl_VertexID];
gl_Position = vec4(tex_coord*2-1, 0, 1);
}
)";
auto circle_program = make_program(vert_source, R"(
#version 140
uniform float sharpness;
uniform vec2 center;
uniform float radius;
uniform int winding;
out float windings;
void main()
{
vec2 pos = gl_FragCoord.xy;
float sdf = length(pos - center) - radius;
windings = winding * 1 / (1 + exp2(sharpness*sdf));
}
)");
auto resolve_program = make_program(vert_source, R"(
#version 140
uniform int view;
uniform bool debug;
uniform float sharpness;
uniform float smoothing;
uniform sampler2D windings;
in vec2 tex_coord;
void main()
{
float winding = texture(windings, tex_coord).r;
if (view == 1) winding = 0.5 + 0.25 * winding; // view packed, remapped
if (view >= 2) winding = clamp(abs(winding), 0, 1); // discard sign/sum
if (view == 3) winding = 0.5 + 0.25 * winding; // view flattened, remapped
if (view >= 4) winding = log2(1 / winding - 1) / sharpness; // unpack (reversible)
if (view == 5) winding = winding / 100; // view unpacked, expanded
if (view >= 6) winding = smoothstep(-smoothing, +smoothing, winding); // smooth
gl_FragColor = vec4(vec3(winding), 1);
bvec3 debug_color = bvec3(
any(lessThan (gl_FragColor, vec4(0))),
any(greaterThan(gl_FragColor, vec4(1))),
any(isinf (gl_FragColor)) ||
any(isnan (gl_FragColor))
);
if (debug && any(debug_color))
gl_FragColor = vec4(debug_color, 1);
}
)");
/// Texture
GLuint windings;
glGenTextures(1, &windings);
glBindTexture(GL_TEXTURE_2D, windings);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
glTexImage2D(
GL_TEXTURE_2D, 0,
GL_R32F,
width, height, 0,
GL_RED, GL_FLOAT, nullptr
);
/// Framebuffer
GLuint framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferTexture(
GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
windings,
0
);
//// Data
struct Circle
{
GLfloat center[2];
GLfloat radius;
GLint winding;
}
circles[] =
{
{{(249+ 69)/2.0+0.5, height-1-(251+ 71)/2.0+0.5}, (251- 71)/2.0, +1},
{{(192+133)/2.0+0.5, height-1-(241+182)/2.0+0.5}, (241-182)/2.0, +1},
{{(138+ 79)/2.0+0.5, height-1-(199+140)/2.0+0.5}, (199-140)/2.0, -1},
{{(274+173)/2.0+0.5, height-1-(134+ 33)/2.0+0.5}, (134- 33)/2.0, -1},
};
//// Main loop
int view = 1;
bool debug = true;
float time = (float)glfwGetTime();
float sharpness = 1;
float smoothing = 5;
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);
glBlendEquation(GL_FUNC_ADD);
while (glfwPollEvents(), !glfwWindowShouldClose(window))
{
///// Time
float pt = time;
float dt = (time = (float)glfwGetTime()) - pt;
///// Input
for (int num = 0; num <= 9; ++num)
if (glfwGetKey(window, GLFW_KEY_0 + num)) view = num;
if (glfwGetKey(window, GLFW_KEY_E)) debug = true;
if (glfwGetKey(window, GLFW_KEY_R)) debug = false;
if (glfwGetKey(window, GLFW_KEY_H)) sharpness -= 0.5F * dt;
if (glfwGetKey(window, GLFW_KEY_L)) sharpness += 0.5F * dt;
if (glfwGetKey(window, GLFW_KEY_J)) smoothing -= 5.0F * dt;
if (glfwGetKey(window, GLFW_KEY_K)) smoothing += 5.0F * dt;
sharpness = std::max(0.1F, sharpness);
smoothing = std::max(0.0F, smoothing);
///// Generate smoothed windings
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(circle_program);
for (auto const & circle : circles)
{
uniform("sharpness", sharpness);
uniform("center", circle.center);
uniform("radius", circle.radius);
uniform("winding", circle.winding);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
///// Resolve
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(resolve_program);
uniform("view", view);
uniform("debug", debug);
uniform("sharpness", sharpness);
uniform("smoothing", smoothing);
glDrawArrays(GL_TRIANGLES, 0, 3);
///// Swap
glfwSwapBuffers(window);
}
//// Cleanup
glfwDestroyWindow(window);
glfwTerminate();
}