Browse Source

Ready. Set. Go!

rexim 3 years ago
commit
bd49883145
10 changed files with 747 additions and 0 deletions
  1. 2 0
      .gitignore
  2. 20 0
      LICENSE
  3. 10 0
      README.md
  4. 6 0
      build.sh
  5. 23 0
      shaders/color.frag
  6. 23 0
      shaders/quad.vert
  7. 94 0
      src/glextloader.c
  8. 343 0
      src/main_opengl.c
  9. 226 0
      src/main_ppm.c
  10. BIN
      thumbnail.png

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+voronoi-*
+*.ppm

+ 20 - 0
LICENSE

@@ -0,0 +1,20 @@
+Copyright 2022 Alexey Kutepov <[email protected]>
+
+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.

+ 10 - 0
README.md

@@ -0,0 +1,10 @@
+# Voronoi with OpenGL using Depth Buffer
+
+![thumbnail](./thumbnail.png)
+
+## Quick Start
+
+```console
+$ ./build.sh
+$ ./voronoi-opengl
+```

+ 6 - 0
build.sh

@@ -0,0 +1,6 @@
+#!/bin/sh
+
+set -xe
+
+cc -Wall -Wextra -o voronoi-ppm src/main_ppm.c
+cc -Wall -Wextra -o voronoi-opengl src/main_opengl.c -lglfw -lGL -lm

+ 23 - 0
shaders/color.frag

@@ -0,0 +1,23 @@
+// Applies animated over time gradient to the user texture.
+#version 330
+
+precision mediump float;
+
+uniform vec2 resolution;
+
+in vec4 color;
+in vec2 seed;
+out vec4 out_color;
+
+#define SEED_MARKER_RADIUS 10
+#define SEED_MARKER_COLOR vec4(.1, .1, .1, 1)
+
+void main(void) {
+    if (length(gl_FragCoord.xy - seed) < SEED_MARKER_RADIUS) {
+        gl_FragDepth = 0;
+        out_color = SEED_MARKER_COLOR;
+    } else {
+        gl_FragDepth = length(gl_FragCoord.xy - seed)/length(resolution);
+        out_color = color;
+    }
+}

+ 23 - 0
shaders/quad.vert

@@ -0,0 +1,23 @@
+// Single triangle strip quad generated entirely on the vertex shader.
+// Simply do glDrawArrays(GL_TRIANGLE_STRIP, 0, 4) and the shader
+// generates 4 points from gl_VertexID. No Vertex Attributes are
+// required.
+#version 330
+
+precision mediump float;
+
+layout(location = 0) in vec2 seed_pos;
+layout(location = 1) in vec4 seed_color;
+
+out vec2 seed;
+out vec4 color;
+
+void main(void)
+{
+    vec2 uv;
+    uv.x = (gl_VertexID & 1);
+    uv.y = ((gl_VertexID >> 1) & 1);
+    gl_Position = vec4(uv * 2.0 - 1.0, 0.0, 1.0);
+    seed  = seed_pos;
+    color = seed_color;
+}

+ 94 - 0
src/glextloader.c

@@ -0,0 +1,94 @@
+static PFNGLCREATESHADERPROC glCreateShader = NULL;
+static PFNGLSHADERSOURCEPROC glShaderSource = NULL;
+static PFNGLCOMPILESHADERPROC glCompileShader = NULL;
+static PFNGLGETSHADERIVPROC glGetShaderiv = NULL;
+static PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog = NULL;
+static PFNGLCREATEPROGRAMPROC glCreateProgram = NULL;
+static PFNGLATTACHSHADERPROC glAttachShader = NULL;
+static PFNGLLINKPROGRAMPROC glLinkProgram = NULL;
+static PFNGLGETPROGRAMIVPROC glGetProgramiv = NULL;
+static PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog = NULL;
+static PFNGLDELETESHADERPROC glDeleteShader = NULL;
+static PFNGLUSEPROGRAMPROC glUseProgram = NULL;
+static PFNGLGENVERTEXARRAYSPROC glGenVertexArrays = NULL;
+static PFNGLBINDVERTEXARRAYPROC glBindVertexArray = NULL;
+static PFNGLDEBUGMESSAGECALLBACKPROC glDebugMessageCallback = NULL;
+static PFNGLDELETEPROGRAMPROC glDeleteProgram = NULL;
+static PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = NULL;
+static PFNGLUNIFORM2FPROC glUniform2f = NULL;
+static PFNGLGENBUFFERSPROC glGenBuffers = NULL;
+static PFNGLBINDBUFFERPROC glBindBuffer = NULL;
+static PFNGLBUFFERDATAPROC glBufferData = NULL;
+static PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray = NULL;
+static PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer = NULL;
+static PFNGLVERTEXATTRIBDIVISORPROC glVertexAttribDivisor = NULL;
+static PFNGLUNIFORM1FPROC glUniform1f = NULL;
+static PFNGLBUFFERSUBDATAPROC glBufferSubData = NULL;
+static PFNGLDRAWARRAYSINSTANCEDPROC glDrawArraysInstanced = NULL;
+static PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers = NULL;
+static PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer = NULL;
+static PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D = NULL;
+static PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatus = NULL;
+static PFNGLUNIFORM1IPROC glUniform1i = NULL;
+static PFNGLDRAWBUFFERSPROC glDrawBuffers = NULL;
+static PFNGLUNIFORM4FPROC glUniform4f = NULL;
+// TODO: there is something fishy with Windows gl.h header
+// Let's try to ship our own gl.h just like glext.h
+#ifdef _WIN32
+static PFNGLACTIVETEXTUREPROC glActiveTexture = NULL;
+#endif // _WIN32
+
+static void load_gl_extensions(void)
+{
+    // TODO: check for failtures?
+    // Maybe some of the functions are not available
+    glCreateShader            = (PFNGLCREATESHADERPROC) glfwGetProcAddress("glCreateShader");
+    glShaderSource            = (PFNGLSHADERSOURCEPROC) glfwGetProcAddress("glShaderSource");
+    glCompileShader           = (PFNGLCOMPILESHADERPROC) glfwGetProcAddress("glCompileShader");
+    glGetShaderiv             = (PFNGLGETSHADERIVPROC) glfwGetProcAddress("glGetShaderiv");
+    glGetShaderInfoLog        = (PFNGLGETSHADERINFOLOGPROC) glfwGetProcAddress("glGetShaderInfoLog");
+    glAttachShader            = (PFNGLATTACHSHADERPROC) glfwGetProcAddress("glAttachShader");
+    glCreateProgram           = (PFNGLCREATEPROGRAMPROC) glfwGetProcAddress("glCreateProgram");
+    glLinkProgram             = (PFNGLLINKPROGRAMPROC) glfwGetProcAddress("glLinkProgram");
+    glGetProgramiv            = (PFNGLGETPROGRAMIVPROC) glfwGetProcAddress("glGetProgramiv");
+    glGetProgramInfoLog       = (PFNGLGETPROGRAMINFOLOGPROC) glfwGetProcAddress("glGetProgramInfoLog");
+    glDeleteShader            = (PFNGLDELETESHADERPROC) glfwGetProcAddress("glDeleteShader");
+    glUseProgram              = (PFNGLUSEPROGRAMPROC) glfwGetProcAddress("glUseProgram");
+    glGenVertexArrays         = (PFNGLGENVERTEXARRAYSPROC) glfwGetProcAddress("glGenVertexArrays");
+    glBindVertexArray         = (PFNGLBINDVERTEXARRAYPROC) glfwGetProcAddress("glBindVertexArray");
+    glDeleteProgram           = (PFNGLDELETEPROGRAMPROC) glfwGetProcAddress("glDeleteProgram");
+    glGetUniformLocation      = (PFNGLGETUNIFORMLOCATIONPROC) glfwGetProcAddress("glGetUniformLocation");
+    glUniform2f               = (PFNGLUNIFORM2FPROC) glfwGetProcAddress("glUniform2f");
+    glGenBuffers              = (PFNGLGENBUFFERSPROC) glfwGetProcAddress("glGenBuffers");
+    glBindBuffer              = (PFNGLBINDBUFFERPROC) glfwGetProcAddress("glBindBuffer");
+    glBufferData              = (PFNGLBUFFERDATAPROC) glfwGetProcAddress("glBufferData");
+    glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC) glfwGetProcAddress("glEnableVertexAttribArray");
+    glVertexAttribPointer     = (PFNGLVERTEXATTRIBPOINTERPROC) glfwGetProcAddress("glVertexAttribPointer");
+    glVertexAttribDivisor     = (PFNGLVERTEXATTRIBDIVISORPROC) glfwGetProcAddress("glVertexAttribDivisor");
+    glUniform1f               = (PFNGLUNIFORM1FPROC) glfwGetProcAddress("glUniform1f");
+    glBufferSubData           = (PFNGLBUFFERSUBDATAPROC) glfwGetProcAddress("glBufferSubData");
+    glGenFramebuffers         = (PFNGLGENFRAMEBUFFERSPROC) glfwGetProcAddress("glGenFramebuffers");
+    glBindFramebuffer         = (PFNGLBINDFRAMEBUFFERPROC) glfwGetProcAddress("glBindFramebuffer");
+    glFramebufferTexture2D    = (PFNGLFRAMEBUFFERTEXTURE2DPROC) glfwGetProcAddress("glFramebufferTexture2D");
+    glCheckFramebufferStatus  = (PFNGLCHECKFRAMEBUFFERSTATUSPROC) glfwGetProcAddress("glCheckFramebufferStatus");
+    glUniform1i               = (PFNGLUNIFORM1IPROC) glfwGetProcAddress("glUniform1i");
+    glDrawBuffers             = (PFNGLDRAWBUFFERSPROC) glfwGetProcAddress("glDrawBuffers");
+    glUniform4f               = (PFNGLUNIFORM4FPROC) glfwGetProcAddress("glUniform4f");
+#ifdef _WIN32
+    glActiveTexture           = (PFNGLACTIVETEXTUREPROC) glfwGetProcAddress("glActiveTexture");
+#endif // _WIN32
+
+    if (glfwExtensionSupported("GL_ARB_debug_output")) {
+        fprintf(stderr, "INFO: ARB_debug_output is supported\n");
+        glDebugMessageCallback = (PFNGLDEBUGMESSAGECALLBACKPROC) glfwGetProcAddress("glDebugMessageCallback");
+    } else {
+        fprintf(stderr, "WARN: ARB_debug_output is NOT supported\n");
+    }
+
+    if (glfwExtensionSupported("GL_EXT_draw_instanced")) {
+        fprintf(stderr, "INFO: EXT_draw_instanced is supported\n");
+        glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDPROC) glfwGetProcAddress("glDrawArraysInstanced");
+    } else {
+        fprintf(stderr, "WARN: EXT_draw_instanced is NOT supported\n");
+    }
+}

+ 343 - 0
src/main_opengl.c

@@ -0,0 +1,343 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <string.h>
+#include <math.h>
+
+#define GLFW_INCLUDE_GLEXT
+#include <GLFW/glfw3.h>
+
+#include "glextloader.c"
+
+#define DEFAULT_SCREEN_WIDTH 1600
+#define DEFAULT_SCREEN_HEIGHT 900
+#define SEEDS_COUNT 10
+
+typedef struct {
+    float x, y;
+} Vector2;
+
+typedef struct {
+    float x, y, z, w;
+} Vector4;
+
+static Vector2 seed_positions[SEEDS_COUNT];
+static Vector4 seed_colors[SEEDS_COUNT];
+static Vector2 seed_velocities[SEEDS_COUNT];
+
+void MessageCallback(GLenum source,
+                     GLenum type,
+                     GLuint id,
+                     GLenum severity,
+                     GLsizei length,
+                     const GLchar* message,
+                     const void* userParam)
+{
+    (void) source;
+    (void) id;
+    (void) length;
+    (void) userParam;
+    fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n",
+            (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""),
+            type, severity, message);
+}
+
+char *slurp_file_into_malloced_cstr(const char *file_path)
+{
+    FILE *f = NULL;
+    char *buffer = NULL;
+
+    f = fopen(file_path, "r");
+    if (f == NULL) goto fail;
+    if (fseek(f, 0, SEEK_END) < 0) goto fail;
+
+    long size = ftell(f);
+    if (size < 0) goto fail;
+
+    buffer = malloc(size + 1);
+    if (buffer == NULL) goto fail;
+
+    if (fseek(f, 0, SEEK_SET) < 0) goto fail;
+
+    fread(buffer, 1, size, f);
+    if (ferror(f)) goto fail;
+
+    buffer[size] = '\0';
+
+    if (f) {
+        fclose(f);
+        errno = 0;
+    }
+    return buffer;
+fail:
+    if (f) {
+        int saved_errno = errno;
+        fclose(f);
+        errno = saved_errno;
+    }
+    if (buffer) {
+        free(buffer);
+    }
+    return NULL;
+}
+
+const char *shader_type_as_cstr(GLuint shader)
+{
+    switch (shader) {
+    case GL_VERTEX_SHADER:
+        return "GL_VERTEX_SHADER";
+    case GL_FRAGMENT_SHADER:
+        return "GL_FRAGMENT_SHADER";
+    default:
+        return "(Unknown)";
+    }
+}
+
+bool compile_shader_source(const GLchar *source, GLenum shader_type, GLuint *shader)
+{
+    *shader = glCreateShader(shader_type);
+    glShaderSource(*shader, 1, &source, NULL);
+    glCompileShader(*shader);
+
+    GLint compiled = 0;
+    glGetShaderiv(*shader, GL_COMPILE_STATUS, &compiled);
+
+    if (!compiled) {
+        GLchar message[1024];
+        GLsizei message_size = 0;
+        glGetShaderInfoLog(*shader, sizeof(message), &message_size, message);
+        fprintf(stderr, "ERROR: could not compile %s\n", shader_type_as_cstr(shader_type));
+        fprintf(stderr, "%.*s\n", message_size, message);
+        return false;
+    }
+
+    return true;
+}
+
+bool compile_shader_file(const char *file_path, GLenum shader_type, GLuint *shader)
+{
+    char *source = slurp_file_into_malloced_cstr(file_path);
+    if (source == NULL) {
+        fprintf(stderr, "ERROR: failed to read file `%s`: %s\n", file_path, strerror(errno));
+        errno = 0;
+        return false;
+    }
+    bool ok = compile_shader_source(source, shader_type, shader);
+    if (!ok) {
+        fprintf(stderr, "ERROR: failed to compile `%s` shader file\n", file_path);
+    }
+    free(source);
+    return ok;
+}
+
+bool link_program(GLuint vert_shader, GLuint frag_shader, GLuint *program)
+{
+    *program = glCreateProgram();
+
+    glAttachShader(*program, vert_shader);
+    glAttachShader(*program, frag_shader);
+    glLinkProgram(*program);
+
+    GLint linked = 0;
+    glGetProgramiv(*program, GL_LINK_STATUS, &linked);
+    if (!linked) {
+        GLsizei message_size = 0;
+        GLchar message[1024];
+
+        glGetProgramInfoLog(*program, sizeof(message), &message_size, message);
+        fprintf(stderr, "Program Linking: %.*s\n", message_size, message);
+    }
+
+    glDeleteShader(vert_shader);
+    glDeleteShader(frag_shader);
+
+    return program;
+}
+
+bool load_shader_program(const char *vertex_file_path,
+                         const char *fragment_file_path,
+                         GLuint *program)
+{
+    GLuint vert = 0;
+    if (!compile_shader_file(vertex_file_path, GL_VERTEX_SHADER, &vert)) {
+        return false;
+    }
+
+    GLuint frag = 0;
+    if (!compile_shader_file(fragment_file_path, GL_FRAGMENT_SHADER, &frag)) {
+        return false;
+    }
+
+    if (!link_program(vert, frag, program)) {
+        return false;
+    }
+
+    return true;
+}
+
+float rand_float(void)
+{
+    return (float) rand() / RAND_MAX;
+}
+
+float lerpf(float a, float b, float t)
+{
+    return a + (b - a)*t;
+}
+
+void generate_random_seeds(void)
+{
+    for (size_t i = 0; i < SEEDS_COUNT; ++i) {
+        seed_positions[i].x = rand_float()*DEFAULT_SCREEN_WIDTH;
+        seed_positions[i].y = rand_float()*DEFAULT_SCREEN_HEIGHT;
+        seed_colors[i].x = rand_float();
+        seed_colors[i].y = rand_float();
+        seed_colors[i].z = rand_float();
+        seed_colors[i].w = 1;
+
+        float angle = rand_float()*2*M_PI;
+        float mag = lerpf(100, 500, rand_float());
+        seed_velocities[i].x = cosf(angle)*mag;
+        seed_velocities[i].y = sinf(angle)*mag;
+    }
+}
+
+int main(void)
+{
+    generate_random_seeds();
+
+    if (!glfwInit()) {
+        fprintf(stderr, "ERROR: could not initialize GLFW\n");
+        exit(1);
+    }
+
+    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
+
+
+    GLFWwindow * const window = glfwCreateWindow(
+                                    DEFAULT_SCREEN_WIDTH,
+                                    DEFAULT_SCREEN_HEIGHT,
+                                    "OpenGL Template",
+                                    NULL,
+                                    NULL);
+    if (window == NULL) {
+        fprintf(stderr, "ERROR: could not create a window.\n");
+        glfwTerminate();
+        exit(1);
+    }
+
+    int gl_ver_major = glfwGetWindowAttrib(window, GLFW_CONTEXT_VERSION_MAJOR);
+    int gl_ver_minor = glfwGetWindowAttrib(window, GLFW_CONTEXT_VERSION_MINOR);
+    printf("OpenGL %d.%d\n", gl_ver_major, gl_ver_minor);
+
+    glfwMakeContextCurrent(window);
+
+    load_gl_extensions();
+
+    if (glDrawArraysInstanced == NULL) {
+        fprintf(stderr, "Support for EXT_draw_instanced is required!\n");
+        exit(1);
+    }
+
+    if (glDebugMessageCallback != NULL) {
+        glEnable(GL_DEBUG_OUTPUT);
+        glDebugMessageCallback(MessageCallback, 0);
+    }
+
+    glEnable(GL_DEPTH_TEST);
+
+    GLuint vao;
+    glGenVertexArrays(1, &vao);
+    glBindVertexArray(vao);
+
+    GLuint positions_vbo;
+    {
+        glGenBuffers(1, &positions_vbo);
+        glBindBuffer(GL_ARRAY_BUFFER, positions_vbo);
+        glBufferData(GL_ARRAY_BUFFER, sizeof(seed_positions), seed_positions, GL_DYNAMIC_DRAW);
+
+#define VA_POS 0
+        glEnableVertexAttribArray(VA_POS);
+        glVertexAttribPointer(VA_POS,
+                              2,
+                              GL_FLOAT,
+                              GL_FALSE,
+                              0,
+                              (void*)0);
+        glVertexAttribDivisor(VA_POS, 1);
+    }
+
+    GLuint colors_vbo;
+    {
+        glGenBuffers(1, &colors_vbo);
+        glBindBuffer(GL_ARRAY_BUFFER, colors_vbo);
+        glBufferData(GL_ARRAY_BUFFER, sizeof(seed_colors), seed_colors, GL_STATIC_DRAW);
+
+#define VA_COLOR 1
+        glEnableVertexAttribArray(VA_COLOR);
+        glVertexAttribPointer(VA_COLOR,
+                              4,
+                              GL_FLOAT,
+                              GL_FALSE,
+                              0,
+                              (void*)0);
+        glVertexAttribDivisor(VA_COLOR, 1);
+    }
+
+    const char *vertex_file_path = "shaders/quad.vert";
+    const char *fragment_file_path = "shaders/color.frag";
+    GLuint program;
+    if (!load_shader_program(vertex_file_path, fragment_file_path, &program)) {
+        exit(1);
+    }
+    glUseProgram(program);
+
+    GLint u_resolution = glGetUniformLocation(program, "resolution");
+    GLint u_color = glGetUniformLocation(program, "color");
+    GLint u_seed = glGetUniformLocation(program, "seed");
+
+    printf("u_resolution = %d\n", u_resolution);
+    printf("u_color = %d\n", u_color);
+    printf("u_seed = %d\n", u_seed);
+
+    double prev_time = 0.0;
+    double delta_time = 0.0f;
+
+    glUniform2f(u_resolution, DEFAULT_SCREEN_WIDTH, DEFAULT_SCREEN_HEIGHT);
+
+    while (!glfwWindowShouldClose(window)) {
+        glClearColor(0.25f, 0.0f, 0.0f, 1.0f);
+        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+        for (size_t i = 0; i < SEEDS_COUNT; ++i) {
+            float x = seed_positions[i].x + seed_velocities[i].x*delta_time;
+            if (0 <= x && x <= DEFAULT_SCREEN_WIDTH) {
+                seed_positions[i].x = x;
+            } else {
+                seed_velocities[i].x *= -1;
+            }
+            float y = seed_positions[i].y + seed_velocities[i].y*delta_time;
+            if (0 <= y && y <= DEFAULT_SCREEN_HEIGHT) {
+                seed_positions[i].y = y;
+            } else {
+                seed_velocities[i].y *= -1;
+            }
+        }
+        glBindBuffer(GL_ARRAY_BUFFER, positions_vbo);
+        glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(seed_positions), seed_positions);
+
+        glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, SEEDS_COUNT);
+
+        glfwSwapBuffers(window);
+        glfwPollEvents();
+
+        double cur_time = glfwGetTime();
+        delta_time = cur_time - prev_time;
+        prev_time = cur_time;
+    }
+
+    return 0;
+}

+ 226 - 0
src/main_ppm.c

@@ -0,0 +1,226 @@
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <limits.h>
+
+#define WIDTH 800
+#define HEIGHT 600
+#define SEEDS_COUNT 20
+
+#define OUTPUT_FILE_PATH "output.ppm"
+
+#define COLOR_WHITE 0xFFFFFFFF
+#define COLOR_BLACK 0xFF000000
+#define COLOR_RED   0xFF0000FF
+#define COLOR_GREEN 0xFF00FF00
+#define COLOR_BLUE  0xFFFF0000
+
+#define GRUVBOX_BRIGHT_RED     0xFF3449FB
+#define GRUVBOX_BRIGHT_GREEN   0xFF26BBB8
+#define GRUVBOX_BRIGHT_YELLOW  0xFF2FBDFA
+#define GRUVBOX_BRIGHT_BLUE    0xFF98A583
+#define GRUVBOX_BRIGHT_PURPLE  0xFF9B86D3
+#define GRUVBOX_BRIGHT_AQUA    0xFF7CC08E
+#define GRUVBOX_BRIGHT_ORANGE  0xFF1980FE
+
+#define BACKGROUND_COLOR 0xFF181818
+
+#define SEED_MARKER_RADIUS 5
+#define SEED_MARKER_COLOR COLOR_BLACK
+
+typedef uint32_t Color32;
+
+typedef struct {
+    int x, y;
+} Point;
+
+typedef struct {
+    uint16_t x;
+    uint16_t y;
+} Point32;
+
+static Color32 image[HEIGHT][WIDTH];
+static int depth[HEIGHT][WIDTH];
+static Point seeds[SEEDS_COUNT];
+static Color32 palette[] = {
+    GRUVBOX_BRIGHT_RED,
+    GRUVBOX_BRIGHT_GREEN,
+    GRUVBOX_BRIGHT_YELLOW,
+    GRUVBOX_BRIGHT_BLUE,
+    GRUVBOX_BRIGHT_PURPLE,
+    GRUVBOX_BRIGHT_AQUA,
+    GRUVBOX_BRIGHT_ORANGE,
+};
+#define palette_count (sizeof(palette)/sizeof(palette[0]))
+
+void fill_image(Color32 color)
+{
+    for (size_t y = 0; y < HEIGHT; ++y) {
+        for (size_t x = 0; x < WIDTH; ++x) {
+            image[y][x] = color;
+        }
+    }
+}
+
+int sqr_dist(int x1, int y1, int x2, int y2)
+{
+    int dx = x1 - x2;
+    int dy = y1 - y2;
+    return dx*dx + dy*dy;
+}
+
+void fill_circle(int cx, int cy, int radius, uint32_t color)
+{
+    // .......
+    // ..***..
+    // ..*@*..
+    // ..***..
+    // .......
+
+    int x0 = cx - radius;
+    int y0 = cy - radius;
+    int x1 = cx + radius;
+    int y1 = cy + radius;
+    for (int x = x0; x <= x1; ++x) {
+        if (0 <= x && x < WIDTH) {
+            for (int y = y0; y <= y1; ++y) {
+                if (0 <= y && y < HEIGHT) {
+                    if (sqr_dist(cx, cy, x, y) <= radius*radius) {
+                        image[y][x] = color;
+                    }
+                }
+            }
+        }
+    }
+}
+
+void save_image_as_ppm(const char *file_path)
+{
+    FILE *f = fopen(file_path, "wb");
+    if (f == NULL) {
+        fprintf(stderr, "ERROR: could write into file %s: %s\n", file_path, strerror(errno));
+        exit(1);
+    }
+    fprintf(f, "P6\n%d %d 255\n", WIDTH, HEIGHT);
+    for (size_t y = 0; y < HEIGHT; ++y) {
+        for (size_t x = 0; x < WIDTH; ++x) {
+            // 0xAABBGGRR
+            uint32_t pixel = image[y][x];
+            uint8_t bytes[3] = {
+                (pixel&0x0000FF)>>8*0,
+                (pixel&0x00FF00)>>8*1,
+                (pixel&0xFF0000)>>8*2,
+            };
+            fwrite(bytes, sizeof(bytes), 1, f);
+            assert(!ferror(f));
+        }
+    }
+
+    int ret = fclose(f);
+    assert(ret == 0);
+}
+
+void generate_random_seeds(void)
+{
+    for (size_t i = 0; i < SEEDS_COUNT; ++i) {
+        seeds[i].x = rand()%WIDTH;
+        seeds[i].y = rand()%HEIGHT;
+    }
+}
+
+void render_seed_markers(void)
+{
+    for (size_t i = 0; i < SEEDS_COUNT; ++i) {
+        fill_circle(seeds[i].x, seeds[i].y, SEED_MARKER_RADIUS, SEED_MARKER_COLOR);
+    }
+}
+
+void render_voronoi_naive(void)
+{
+    for (int y = 0; y < HEIGHT; ++y) {
+        for (int x = 0; x < WIDTH; ++x) {
+            int j = 0;
+            for (size_t i = 1; i < SEEDS_COUNT; ++i) {
+                if (sqr_dist(seeds[i].x, seeds[i].y, x, y) < sqr_dist(seeds[j].x, seeds[j].y, x, y)) {
+                    j = i;
+                }
+            }
+            image[y][x] = palette[j%palette_count];
+        }
+    }
+}
+
+Color32 point_to_color(Point p)
+{
+    assert(p.x >= 0);
+    assert(p.y >= 0);
+    assert(p.x < UINT16_MAX);
+    assert(p.y < UINT16_MAX);
+    uint16_t x = p.x;
+    uint16_t y = p.y;
+    return (y<<16) | x;
+}
+
+Point color_to_point(Color32 c)
+{
+    return (Point) {
+        .x = (c&0x0000FFFF)>>0,
+        .y = (c&0xFFFF0000)>>16
+    };
+}
+
+void render_point_gradient(void)
+{
+    for (int y = 0; y < HEIGHT; ++y) {
+        for (int x = 0; x < WIDTH; ++x) {
+            Point p = {x, y};
+            image[y][x] = point_to_color(p);
+        }
+    }
+}
+
+void apply_next_seed(size_t seed_index)
+{
+    Point seed = seeds[seed_index];
+    Color32 color = palette[seed_index%palette_count];
+
+    for (int y = 0; y < HEIGHT; ++y) {
+        for (int x = 0; x < WIDTH; ++x) {
+            int dx = x - seed.x;
+            int dy = y - seed.y;
+            int d = dx*dx + dy*dy;
+            if (d < depth[y][x]) {
+                depth[y][x] = d;
+                image[y][x] = color;
+            }
+        }
+    }
+}
+
+void render_voronoi_interesting(void)
+{
+    for (int y = 0; y < HEIGHT; ++y) {
+        for (int x = 0; x < WIDTH; ++x) {
+            depth[y][x] = INT_MAX;
+        }
+    }
+
+    for (size_t i = 0; i < SEEDS_COUNT; ++i) {
+        apply_next_seed(i);
+    }
+}
+
+int main(void)
+{
+    srand(time(0));
+    fill_image(BACKGROUND_COLOR);
+    generate_random_seeds();
+    render_voronoi_interesting();
+    render_seed_markers();
+    save_image_as_ppm(OUTPUT_FILE_PATH);
+    return 0;
+}

BIN
thumbnail.png